feat: activate saas

This commit is contained in:
Vasily Zubarev
2025-04-24 19:46:56 +02:00
parent fd142762af
commit b4045930e2
7 changed files with 99 additions and 164 deletions

View File

@@ -3,6 +3,7 @@ import { Card, CardContent, CardTitle } from "@/components/ui/card"
import { ColoredText } from "@/components/ui/colored-text" import { ColoredText } from "@/components/ui/colored-text"
import config from "@/lib/config" import config from "@/lib/config"
import { PLANS } from "@/lib/stripe" import { PLANS } from "@/lib/stripe"
import { Mail } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
@@ -15,7 +16,8 @@ export default async function ChoosePlanPage() {
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-8">
<Card className="w-full max-w-4xl mx-auto p-8 flex flex-col items-center justify-center gap-8"> <Card className="w-full max-w-4xl mx-auto p-8 flex flex-col items-center justify-center gap-8">
<CardTitle className="text-4xl font-bold text-center"> <CardTitle className="text-4xl font-bold text-center">
<ColoredText>Choose your Cloud Edition plan</ColoredText> <ColoredText>TaxHacker Cloud Edition</ColoredText>
<h2 className="mt-3 text-2xl font-semibold text-muted-foreground">Choose your plan</h2>
</CardTitle> </CardTitle>
<CardContent className="w-full"> <CardContent className="w-full">
{config.auth.disableSignup ? ( {config.auth.disableSignup ? (
@@ -23,6 +25,7 @@ export default async function ChoosePlanPage() {
Creating new account is disabled for now. Please use the self-hosted version. Creating new account is disabled for now. Please use the self-hosted version.
</div> </div>
) : ( ) : (
<div className="space-y-8">
<div className="flex flex-wrap justify-center gap-8"> <div className="flex flex-wrap justify-center gap-8">
{Object.values(PLANS) {Object.values(PLANS)
.filter((plan) => plan.isAvailable) .filter((plan) => plan.isAvailable)
@@ -30,10 +33,31 @@ export default async function ChoosePlanPage() {
<PricingCard key={plan.code} plan={plan} /> <PricingCard key={plan.code} plan={plan} />
))} ))}
</div> </div>
<div className="text-center text-muted-foreground">
By signing up, you agree to our{" "}
<Link href="/docs/terms" className="hover:text-primary transition-colors underline">
Terms of Service
</Link>
,{" "}
<Link href="/docs/privacy_policy" className="hover:text-primary transition-colors underline">
Privacy Policy
</Link>
, and{" "}
<Link href="/docs/ai" className="hover:text-primary transition-colors underline">
AI Usage Disclosure
</Link>
</div>
</div>
)} )}
</CardContent> </CardContent>
<div className="text-center text-muted-foreground"> <div className="text-center text-muted-foreground">
<Link href={`mailto:${config.app.supportEmail}`} className="hover:text-primary transition-colors"> <Link
href={`mailto:${config.app.supportEmail}`}
className="flex flex-row gap-1 items-center hover:text-primary transition-colors underline"
>
<Mail className="w-4 h-4" />
Contact us for custom plans Contact us for custom plans
</Link> </Link>
</div> </div>

View File

@@ -45,7 +45,7 @@ export default async function AI() {
</p> </p>
<ul className="list-disc pl-6 space-y-2 mb-6 text-gray-700"> <ul className="list-disc pl-6 space-y-2 mb-6 text-gray-700">
<li> <li>
<strong>GPT-4o</strong> and <strong>GPT-4.1</strong> <strong>gpt-4o-mini</strong> and <strong>gpt-4.1-mini</strong>
</li> </li>
</ul> </ul>
<p className="text-gray-700 leading-relaxed mb-6"> <p className="text-gray-700 leading-relaxed mb-6">

View File

@@ -15,10 +15,16 @@ export default function LandingPage() {
</Link> </Link>
<div className="flex gap-4"> <div className="flex gap-4">
<Link <Link
href="#start" href="/enter"
className="text-sm font-medium px-4 py-2 rounded-full border border-gray-200 hover:bg-gray-50 transition-all"
>
Log In
</Link>
<Link
href="/cloud"
className="text-sm font-medium bg-gradient-to-r from-blue-600 to-indigo-600 text-white px-4 py-2 rounded-full hover:opacity-90 transition-all" className="text-sm font-medium bg-gradient-to-r from-blue-600 to-indigo-600 text-white px-4 py-2 rounded-full hover:opacity-90 transition-all"
> >
Get Started Sign Up
</Link> </Link>
</div> </div>
</div> </div>
@@ -35,7 +41,7 @@ export default function LandingPage() {
Let AI finally care about your taxes, scan your receipts and analyze your expenses Let AI finally care about your taxes, scan your receipts and analyze your expenses
</h1> </h1>
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto"> <p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
A self-hosted accounting app crafted with love for freelancers and small businesses. A self-hosted accounting app crafted with love for freelancers and small businesses
</p> </p>
<div className="flex gap-4 justify-center"> <div className="flex gap-4 justify-center">
<Link <Link
@@ -237,42 +243,7 @@ export default function LandingPage() {
Choose Your Version of TaxHacker Choose Your Version of TaxHacker
</h2> </h2>
</div> </div>
<div className="grid md:grid-cols-2 gap-8"> <div className="grid md:grid-cols-2 gap-16">
{/* Self-Hosted Version */}
<div className="bg-gradient-to-b from-white to-gray-50 p-8 rounded-2xl shadow-lg ring-1 ring-gray-100">
<div className="inline-block px-3 py-1 rounded-full bg-violet-50 text-violet-600 text-sm font-medium mb-4">
Use Your Own Server
</div>
<h3 className="text-2xl font-semibold mb-4">
<ColoredText>Self-Hosted Edition</ColoredText>
</h3>
<ul className="space-y-3 text-gray-600 mb-8">
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Complete control over your data
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Use at your own infrastructure
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Free and open source
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Bring your own OpenAI keys
</li>
</ul>
<Link
href="https://github.com/vas3k/TaxHacker"
target="_blank"
className="block w-full text-center px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-medium rounded-full hover:opacity-90 transition-all shadow-lg shadow-blue-500/20"
>
Github + Docker Compose
</Link>
</div>
{/* Cloud Version */} {/* Cloud Version */}
<div className="bg-gradient-to-b from-white to-gray-50 p-8 rounded-2xl shadow-lg ring-1 ring-gray-100"> <div className="bg-gradient-to-b from-white to-gray-50 p-8 rounded-2xl shadow-lg ring-1 ring-gray-100">
<div className="absolute top-4 right-4"> <div className="absolute top-4 right-4">
@@ -302,12 +273,47 @@ export default function LandingPage() {
Automatic updates and new features Automatic updates and new features
</li> </li>
</ul> </ul>
<button <Link
disabled href="/cloud"
className="block w-full text-center px-6 py-3 bg-gray-100 text-gray-400 font-medium rounded-full cursor-not-allowed" className="block w-full text-center px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-medium rounded-full hover:opacity-90 transition-all shadow-lg shadow-blue-500/20"
> >
Coming Soon LET'S GO!
</button> </Link>
</div>
{/* Self-Hosted Version */}
<div className="bg-gradient-to-b from-white to-gray-50 p-8 rounded-2xl shadow-lg ring-1 ring-gray-100">
<div className="inline-block px-3 py-1 rounded-full bg-violet-50 text-violet-600 text-sm font-medium mb-4">
Use Your Own Server
</div>
<h3 className="text-2xl font-semibold mb-4">
<ColoredText>Self-Hosted Edition</ColoredText>
</h3>
<ul className="space-y-3 text-gray-600 mb-8">
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Free and open source
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Complete control over your data
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Use at your own infrastructure
</li>
<li className="flex items-center">
<span className="text-blue-600 mr-2"></span>
Bring your own OpenAI keys
</li>
</ul>
<Link
href="https://github.com/vas3k/TaxHacker"
target="_blank"
className="block w-full text-center px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-medium rounded-full hover:opacity-90 transition-all shadow-lg shadow-blue-500/20"
>
Github + Docker Compose
</Link>
</div> </div>
</div> </div>
</div> </div>
@@ -431,7 +437,7 @@ export default function LandingPage() {
<div className="max-w-7xl mx-auto text-center text-sm text-gray-500"> <div className="max-w-7xl mx-auto text-center text-sm text-gray-500">
Made with in Berlin by{" "} Made with in Berlin by{" "}
<Link href="https://github.com/vas3k" className="underline"> <Link href="https://github.com/vas3k" className="underline">
vas3k @vas3k
</Link> </Link>
</div> </div>
@@ -453,6 +459,13 @@ export default function LandingPage() {
<Link href="/docs/cookie" className="text-sm text-gray-600 hover:text-gray-900"> <Link href="/docs/cookie" className="text-sm text-gray-600 hover:text-gray-900">
Cookie Policy Cookie Policy
</Link> </Link>
<Link
href="https://github.com/vas3k/TaxHacker"
target="_blank"
className="text-sm text-gray-600 hover:text-gray-900"
>
Source Code
</Link>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,96 +0,0 @@
"use client"
import { FormInput } from "@/components/forms/simple"
import { Button } from "@/components/ui/button"
import { authClient } from "@/lib/auth-client"
import { useRouter } from "next/navigation"
import { useState } from "react"
export default function SignupForm() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [otp, setOtp] = useState("")
const [isOtpSent, setIsOtpSent] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const router = useRouter()
const handleSendOtp = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)
try {
await authClient.emailOtp.sendVerificationOtp({
email,
type: "sign-in",
})
setIsOtpSent(true)
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to send OTP")
} finally {
setIsLoading(false)
}
}
const handleVerifyOtp = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)
try {
await authClient.signIn.emailOtp({
email,
otp,
})
await authClient.updateUser({
name,
})
router.push("/dashboard")
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to verify OTP")
} finally {
setIsLoading(false)
}
}
return (
<form onSubmit={isOtpSent ? handleVerifyOtp : handleSendOtp} className="flex flex-col gap-4 w-full">
<FormInput
title="Your Name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
disabled={isOtpSent}
/>
<FormInput
title="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
disabled={isOtpSent}
/>
{isOtpSent && (
<FormInput
title="Check your email for the verification code"
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value)}
required
maxLength={6}
pattern="[0-9]{6}"
/>
)}
{error && <p className="text-red-500 text-sm">{error}</p>}
<Button type="submit" disabled={isLoading}>
{isLoading ? "Loading..." : isOtpSent ? "Verify Code" : "Create Account"}
</Button>
</form>
)
}

View File

@@ -34,8 +34,8 @@ export async function WelcomeWidget() {
</CardTitle> </CardTitle>
<CardDescription className="mt-5"> <CardDescription className="mt-5">
<p className="mb-3"> <p className="mb-3">
I&apos;m a little accountant app that tries to help you deal with endless receipts, checks and invoices with I&apos;m a little accountant app that helps you deal with endless receipts, checks and invoices with (you
(you guessed it) GenAI. Here&apos;s what I can do: guessed it) AI. Here&apos;s what I can do:
</p> </p>
<ul className="mb-5 list-disc pl-5 space-y-1"> <ul className="mb-5 list-disc pl-5 space-y-1">
<li> <li>
@@ -50,7 +50,7 @@ export async function WelcomeWidget() {
</li> </li>
<li> <li>
All <strong>LLM prompts are configurable</strong>: for fields, categories and projects. You can go to All <strong>LLM prompts are configurable</strong>: for fields, categories and projects. You can go to
settings and change them. settings and change them however you want.
</li> </li>
<li> <li>
I save data in a <strong>local SQLite database</strong> and can export it to CSV and ZIP archives. I save data in a <strong>local SQLite database</strong> and can export it to CSV and ZIP archives.

View File

@@ -51,16 +51,9 @@ export default function SidebarUser({ profile, isSelfHosted }: { profile: UserPr
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href="/settings/profile" className="flex items-center gap-2"> <Link href="/settings/profile" className="flex items-center gap-2">
<User className="h-4 w-4" /> <User className="h-4 w-4" />
Profile Settings Profile & Plan
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
{/* <DropdownMenuItem asChild>
<Link href="/settings/billing" className="flex items-center gap-2">
<CreditCard className="h-4 w-4" />
Your Subscription
</Link>
</DropdownMenuItem> */}
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href="/settings/profile" className="flex items-center gap-2"> <Link href="/settings/profile" className="flex items-center gap-2">
<HardDrive className="h-4 w-4" /> <HardDrive className="h-4 w-4" />

View File

@@ -38,15 +38,16 @@ export const PLANS: Record<string, Plan> = {
early: { early: {
code: "early", code: "early",
name: "Early Adopter", name: "Early Adopter",
description: "Special plan for our early users", description: "Discounted plan for our first users who can forgive us bugs and childish problems :)",
benefits: [ benefits: [
"Special price for early adopters",
"512 Mb of storage", "512 Mb of storage",
"1000 AI file analysis", "1000 AI file analysis",
"Unlimited transactions", "Unlimited transactions",
"Unlimited fields, categories and projects", "Unlimited fields, categories and projects",
], ],
price: "€50/year", price: "€35 for a year",
stripePriceId: "price_1RGzKUPKOUEUzVB3hVyo2n57", stripePriceId: "price_1RHTmTAs8DS4NhOzGnWqxvZC",
limits: { limits: {
storage: 512 * 1024 * 1024, storage: 512 * 1024 * 1024,
ai: 1000, ai: 1000,