Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for bot upgrades #268

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
969 changes: 915 additions & 54 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
"version": "0.1.0",
"homepage": "https://pyclashbot.app/",
"dependencies": {
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.427.0",
"next": "^14.2.5",
"next-auth": "^5.0.0-beta.18",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-ga4": "^2.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GET, POST } from "@/auth/auth";
12 changes: 12 additions & 0 deletions src/app/auth/error/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function AuthErrorPage({
params,
}: Readonly<{ params: { error: string } }>) {
class AuthError extends Error {
constructor(message: string) {
super(message);
this.name = "Authentication Error";
}
}

throw new AuthError(params.error);
}
19 changes: 13 additions & 6 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Footer from "@/components/Footer";
import NavBar from "@/components/NavBar";
import Provider from "@/components/providers/GlobalProvider";
import "@/styles/global.css";
import { Gantari as Font } from "next/font/google";
import Script from "next/script";
Expand Down Expand Up @@ -63,12 +65,17 @@ export default async function RootLayout({
/>
<link rel="canonical" href="https://www.pyclashbot.app/" />
</head>
<body className={`${font.className} antialiased`}>
<div className="mx-auto flex w-11/12 max-w-screen-md flex-col">
<div className="flex min-h-screen flex-col">{children}</div>
<Footer />
</div>
</body>
<Provider>
<body className={`${font.className} antialiased`}>
<div className="mx-auto flex w-11/12 max-w-screen-md flex-col">
<div className="flex min-h-screen flex-col">
<NavBar />
{children}
</div>
<Footer />
</div>
</body>
</Provider>
</html>
);
}
45 changes: 45 additions & 0 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { auth } from "@/auth/auth";
import { UserAuthForm } from "@/components/AuthForm";
import Link from "next/link";
import { redirect } from "next/navigation";

export default async function AuthenticationPage({
searchParams,
}: Readonly<{
searchParams: { [key: string]: string | string[] | undefined };
}>) {
const callbackUrl = searchParams.callbackUrl as string | undefined;
const session = await auth();

if (session) {
redirect(callbackUrl ?? "/");
}

return (
<div className="lg:p-8">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<h1 className="text-center text-2xl font-semibold tracking-tight">
Sign in to <span className="text-primary">pyclashbot.app</span>
</h1>
<UserAuthForm />
<p className="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</Link>
.
</p>
</div>
</div>
);
}
154 changes: 154 additions & 0 deletions src/app/plans/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardTitle,
CardHeader,
} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import Link from "next/link";
import { ReactNode } from "react";

type Plan = {
name: string;
price: string;
features: ReactNode[];
planned?: boolean;
};

export default async function Page() {
const pricing: Plan[] = [
{
name: "Starter",
price: "Free",
features: [
"Free use of community features",
"Automated Farming",
"Hands-free Gaming",
],
},
{
planned: true,
name: "Pro",
price: "$/month",
features: [
"Improved Fighting Strategies",
"Advanced Farming",
"Everything in Starter",
],
},
{
planned: true,
name: "Unlimited",
price: "$$/month",
features: ["Up to XX Clients", "Priority support", "Everything in Pro"],
},
];

return (
<div className="flex flex-col space-y-6 text-center">
<div className="flex flex-col space-y-2 text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Choose your bot plan
</h1>
</div>
<Separator />
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{pricing.map((plan) => (
<PlanWrapper key={plan.name} plan={plan}>
<Card className="flex h-full w-full flex-col justify-between">
<div>
<CardHeader>
<CardTitle>{plan.name}</CardTitle>
<CardDescription>{plan.price}</CardDescription>
</CardHeader>
<CardContent>
<ul className="list-inside list-disc text-left">
{plan.features
.filter((feature) => feature !== null)
.map((feature) => (
<li
className="w-full overflow-hidden"
key={feature?.toString()}
>
{feature}
</li>
))}
</ul>
</CardContent>
</div>
<CardFooter>
{plan.price === "Free" ? (
<Button
className="w-full"
data-umami-event="Pricing - Get Started"
asChild
>
<Link href={"/"}>
Get started
<svg
className="ms-2 h-3.5 w-3.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 5h12m0 0L9 1m4 4L9 9"
/>
</svg>
</Link>
</Button>
) : (
<Button
disabled
style={{ pointerEvents: "none" }}
data-umami-event={`Pricing - ${plan.name} - Buy Now`}
className="w-full"
variant="secondary"
>
Subscribe
</Button>
)}
</CardFooter>
</Card>
</PlanWrapper>
))}
</div>
</div>
);
}

function PlanWrapper({
plan,
children,
}: Readonly<{ plan: Plan; children: ReactNode }>) {
if (!plan.planned) {
return <>{children}</>;
}

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="select-none blur-sm" aria-hidden asChild>
{children}
</TooltipTrigger>
<TooltipContent sideOffset={-80} align="center">
Coming soon
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
31 changes: 31 additions & 0 deletions src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export default function Page() {
return (
<section className="flex w-full flex-col gap-2">
<h2 className="mb-2 text-2xl">Privacy Policy</h2>
<h3 className="border-b text-xl">Section 1: Information Collection</h3>
<h4 className="text-lg">1.1. Anonymous Usage Data</h4>
<p>
We collect anonymous usage data to improve our services continually.
This data helps us enhance the user experience and is used solely for
analytical purposes.
</p>
<h4 className="text-lg">1.2. Account Information</h4>
<p>
Your account information is limited to what is essential for login
purposes and is securely shared with OAuth providers, such as Google.
Future integrations with additional providers will adhere to the same
stringent security standards.
</p>
<h3 className="mt-1 border-b text-xl">
Section 2: User Responsibilities
</h3>
<h4 className="text-lg">2.1. Legal and Appropriate Use</h4>
<p>
Users are expected to comply with all local legal and contractual
requirements regarding use of pyclashbot.app. Any misuse, including
spamming the API, will be considered a violation of our terms and may
result in the termination of your account.
</p>
</section>
);
}
26 changes: 26 additions & 0 deletions src/app/terms/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function Page() {
return (
<section className="flex w-full flex-col gap-2">
<h2 className="mb-2 text-2xl">Terms of Service</h2>
<h3 className="border-b text-xl">Section 1: Acceptance of Terms</h3>
<h4 className="text-lg">1.1. User Agreement</h4>
<p>
By accessing and using pyclashbot.app, you agree to abide by these terms
and conditions. If you do not agree, refrain from using our services.
</p>
<h3 className="border-b text-xl">Section 2: Prohibited Activities</h3>
<h4 className="text-lg">2.1. Abuse of Services</h4>
<p>
Users are prohibited from abusing the pyclashbot.app API, including but
not limited to spamming. Such activities may result in the suspension or
termination of your account.
</p>
<h4 className="text-lg">2.2. Legal Compliance</h4>
<p>
Users must ensure that all content communicated with pyclashbot.app
complies with applicable laws and regulations. Violation of this
provision may lead to account termination and legal action.
</p>
</section>
);
}
27 changes: 27 additions & 0 deletions src/auth/auth.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { type User, type NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";
import { z } from "zod";

const schema = z.object({
NEXTAUTH_SECRET: z.string(),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
});

const env = schema.parse(process.env);

export default {
debug: process.env.NODE_ENV === "development",
secret: env.NEXTAUTH_SECRET,
providers: [
Google({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true,
}),
],
pages: {
signIn: "/login",
error: "/auth/error",
},
} satisfies NextAuthConfig;
16 changes: 16 additions & 0 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import authConfig from "./auth.config";
// import { PrismaAdapter } from "@auth/prisma-adapter";
// import prisma from "@repo/database/src/client";
import NextAuth from "next-auth";

export const {
handlers: { GET, POST },
auth,
} = NextAuth({
// adapter: PrismaAdapter(prisma),
session: {
strategy: "jwt",
maxAge: 24 * 60 * 60, // 24 hours
},
...authConfig,
});
Loading
Loading