Skip to content

Commit 7226d32

Browse files
authored
Merge pull request #22 from will-lp1/new-landingpage
New landingpage
2 parents 355b5ba + 881bfd5 commit 7226d32

File tree

18 files changed

+907
-348
lines changed

18 files changed

+907
-348
lines changed

README.md

Lines changed: 63 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,72 @@
1-
# Snow Leopard - AI-Powered Writing Assistant
1+
# Snow Leopard
22

3-
## What is Snow Leopard?
4-
Snow Leopard is an intelligent writing environment designed to enhance your writing process with AI capabilities. It provides a seamless interface for document creation, editing, and collaboration, augmented by AI suggestions, content generation, and contextual chat.
5-
6-
## Why Snow Leopard?
7-
Modern writing tools often lack deep AI integration or are closed-source. Snow Leopard aims to provide:
8-
9-
**Open-Source & Extensible** – Transparent development and easy integration.
10-
🦾 **AI Driven** - Enhance your writing with AI suggestions, generation, and chat context using the Vercel AI SDK.
11-
🔒 **Data Privacy Focused** – Your documents, your data. Designed with privacy in mind.
12-
⚙️ **Self-Hosting Option** – Flexibility to run your own instance.
13-
📄 **Rich Document Editing** – Supports various content types and formats.
14-
🎨 **Modern UI & UX** – Clean, intuitive interface built with Shadcn UI and TailwindCSS.
15-
🚀 **Developer-Friendly** – Built with Next.js and Drizzle for easy customization.
16-
17-
---
3+
Monorepo:
4+
- **apps/snow-leopard**: Next.js app
5+
- **packages/db**: Drizzle schema & migrations
186

197
## Tech Stack
20-
* **Framework:** Next.js (App Router)
21-
* **Language:** TypeScript
22-
* **UI:** React, TailwindCSS, Shadcn UI
23-
* **Database:** PostgreSQL
24-
* **ORM:** Drizzle ORM
25-
* **Authentication:** Better Auth
26-
* **AI Integration:** Vercel AI SDK (configurable for various providers like Groq, OpenAI, Anthropic, etc.)
27-
28-
---
29-
30-
## Getting Started
31-
32-
### Prerequisites
33-
Make sure you have the following installed:
34-
* **Node.js:** `v18` or higher
35-
* **pnpm:** `v8` or higher (Recommended package manager)
36-
* **Docker & Docker Compose:** `v20` or higher
37-
38-
### Setup (Monorepo)
39-
40-
This project uses a monorepo structure managed by pnpm workspaces:
41-
* `apps/snow-leopard`: The Next.js web application.
42-
* `packages/db`: Shared database schema, client, and migration logic.
43-
44-
Follow these steps **from the project root directory**:
45-
46-
1. **Clone & Install Dependencies**
47-
```bash
48-
git clone https://github.com/will-lp1/snowleopard.git
49-
cd snowleopard
50-
pnpm install # Installs dependencies for all packages/apps
51-
```
52-
53-
2. **Create Database `.env` File**
54-
Copy the example environment file for the database package:
55-
```bash
56-
cp packages/db/.env.example packages/db/.env
57-
```
58-
59-
3. **Create Application `.env` File**
60-
Copy the example environment file for the Next.js application:
61-
```bash
62-
cp apps/snow-leopard/.env.example apps/snow-leopard/.env
63-
```
64-
65-
4. **Configure Environment Variables**
66-
* Edit **BOTH** `.env` files:
67-
* `packages/db/.env`
68-
* `apps/snow-leopard/.env`
69-
* Ensure the `DATABASE_URL` in **both files** matches the one used by Docker Compose (default below):
70-
```dotenv
71-
# In BOTH apps/snow-leopard/.env AND packages/db/.env
72-
DATABASE_URL="postgresql://user:password@localhost:5432/cursorforwriting_db"
73-
```
74-
* Configure other necessary variables (like `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `GROQ_API_KEY`) **only** in `apps/snow-leopard/.env`. See the **Environment Variables** section below for details.
75-
76-
5. **Start the Database (using Docker)**
77-
* Ensure Docker Desktop is running.
78-
* Start the PostgreSQL service defined in `docker-compose.yml`:
79-
```bash
80-
docker compose up -d
81-
```
82-
* *Wait a few moments for the database container to initialize.*
83-
84-
6. **Apply Database Schema**
85-
* Push the schema defined in `packages/db/src/schema.ts` to the running database:
86-
```bash
87-
pnpm db:push
88-
```
89-
90-
7. **Start the Development Server**
91-
* Start the Next.js application:
92-
```bash
93-
pnpm dev
94-
```
95-
96-
8. **Open in Browser**
97-
* Visit [`http://localhost:3000`](http://localhost:3000)
98-
99-
---
100-
101-
### Environment Variables
102-
Configure the following primarily in your **`apps/snow-leopard/.env`** file:
103-
104-
```dotenv
105-
# === Database ===
106-
# (Required for App - Ensure this matches your docker-compose.yml & packages/db/.env)
107-
DATABASE_URL="postgresql://user:password@localhost:5432/cursorforwriting_db"
108-
109-
# === Better Auth ===
110-
# (Required)
111-
BETTER_AUTH_SECRET="" # Generate a strong secret (e.g., `openssl rand -hex 32` or via https://www.better-auth.com/docs/installation)
112-
BETTER_AUTH_URL="http://localhost:3000" # Base URL of your app
113-
114-
# === Feedback ===
115-
# (Required for Feedback)s
116-
DISCORD_WEBHOOK_URL=""
117-
118-
# === AI Provider(s) ===
119-
# (Required for AI features)
120-
# Add API keys for the providers you want to use.
121-
# These can be configured in apps/snow-leopard/lib/ai/providers.ts
122-
# See Vercel AI SDK Docs for more providers: https://sdk.vercel.ai/providers/ai-sdk-providers
123-
124-
# Example for Groq:
125-
GROQ_API_KEY="" # Get your key at https://console.groq.com/keys
126-
127-
# Example for OpenAI:
128-
# OPENAI_API_KEY=""
129-
130-
# Example for Anthropic:
131-
# ANTHROPIC_API_KEY=""
132-
```
133-
134-
**Important Note:** The `packages/db/.env` file **only** needs the `DATABASE_URL` for Drizzle Kit commands (like `db:push`, `db:generate`, `db:migrate`, `db:studio`).
135-
136-
---
137-
138-
### Database Management (Drizzle ORM)
139-
140-
* **Location:** Schema (`packages/db/src/schema.ts`), Client (`src/index.ts`), and Migrations (`migrations/`) are in `packages/db`.
141-
* **Commands (Run from Root):**
142-
* Start Local DB: `docker compose up -d`
143-
* Stop Local DB: `docker compose down`
144-
* Apply Schema Changes (Simple Sync): `pnpm db:push`
145-
* Generate Migration File (after schema changes): `pnpm db:generate`
146-
* Apply Migrations: `pnpm db:migrate`
147-
* Open DB Studio (Web UI): `pnpm db:studio`
148-
149-
---
150-
151-
### Authentication (Better Auth)
152-
153-
* **Configuration:** Set `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` in `apps/snow-leopard/.env`.
154-
* **Adapter:** Uses the Drizzle adapter, configured in `apps/snow-leopard/lib/auth.ts` (imports `db` from `@snow-leopard/db`).
155-
* **Schema:** Requires `user`, `session`, `account`, `verification` tables (defined in `packages/db/src/schema.ts`). `pnpm db:push` applies these.
156-
157-
---
8+
- Next.js (App Router), TypeScript, React
9+
- TailwindCSS, Shadcn UI
10+
- PostgreSQL, Drizzle ORM
11+
- Better Auth
12+
- Vercel AI SDK
13+
14+
## Setup
15+
16+
From the project root:
17+
18+
1. Clone & install:
19+
```bash
20+
git clone https://github.com/will-lp1/snowleopard.git
21+
cd snowleopard
22+
pnpm install
23+
```
24+
25+
2. Copy env files:
26+
```bash
27+
cp packages/db/.env.example packages/db/.env
28+
cp apps/snow-leopard/.env.example apps/snow-leopard/.env
29+
```
30+
- Set **DATABASE_URL** in both.
31+
- In `apps/snow-leopard/.env`, add:
32+
```dotenv
33+
BETTER_AUTH_SECRET="" # e.g., openssl rand -hex 32
34+
BETTER_AUTH_URL="http://localhost:3000"
35+
DISCORD_WEBHOOK_URL=""
36+
# AI Keys: GROQ_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY
37+
```
38+
39+
3. Start the database:
40+
```bash
41+
docker compose up -d
42+
```
43+
44+
4. Apply schema:
45+
```bash
46+
pnpm db:push
47+
```
48+
49+
5. Run the app:
50+
```bash
51+
pnpm dev
52+
```
53+
Open [http://localhost:3000](http://localhost:3000).
54+
55+
## Database Management
56+
57+
- **Start**: `docker compose up -d`
58+
- **Stop**: `docker compose down`
59+
- **Sync schema**: `pnpm db:push`
60+
- **Generate migration**: `pnpm db:generate`
61+
- **Apply migrations**: `pnpm db:migrate`
62+
- **Studio**: `pnpm db:studio`
15863

15964
## Contributing
16065

161-
We welcome contributions! Please follow these steps:
162-
163-
1. **Fork** the repository.
164-
2. **Create a new branch** (`git checkout -b feature/your-feature` or `fix/your-bug-fix`).
165-
4. **Commit** your changes with clear messages.
166-
5. **Push** your branch to your fork.
167-
6. **Open a Pull Request** against the `main` branch.
168-
169-
Please provide a detailed description of your changes in the PR. Link to any relevant issues.
170-
171-
For bug reports and feature requests, please use GitHub Issues or the in-app feedback widget.
172-
173-
---
66+
1. Fork the repo.
67+
2. Create a branch (`feature/...` or `fix/...`).
68+
3. Commit, push, and open a PR.
17469

17570
## License
17671

177-
This project is licensed under the **Apache License 2.0**. See the [LICENSE](LICENSE) file for details.
72+
Apache License 2.0. See [LICENSE](LICENSE) for details.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { NextResponse } from 'next/server';
2+
import { getSession } from '@/app/(auth)/auth';
3+
import { db } from '@snow-leopard/db';
4+
import * as schema from '@snow-leopard/db';
5+
import { randomUUID } from 'crypto';
6+
import { eq, inArray, desc, and } from 'drizzle-orm';
7+
8+
export const dynamic = 'force-dynamic';
9+
10+
export async function POST() {
11+
const session = await getSession();
12+
if (!session?.user?.id) {
13+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
14+
}
15+
16+
const userId = session.user.id;
17+
const now = new Date();
18+
19+
const existing = await db
20+
.select()
21+
.from(schema.subscription)
22+
.where(
23+
and(
24+
eq(schema.subscription.referenceId, userId),
25+
inArray(schema.subscription.status, ['active', 'trialing'])
26+
)
27+
)
28+
.orderBy(desc(schema.subscription.createdAt))
29+
.limit(1);
30+
31+
if (existing.length > 0) {
32+
const current = existing[0];
33+
// If already active subscription
34+
if (current.status === 'active') {
35+
return NextResponse.json({ alreadyActive: true });
36+
}
37+
// If already in trial and not expired
38+
if (
39+
current.status === 'trialing' &&
40+
current.trialEnd &&
41+
new Date(current.trialEnd) > now
42+
) {
43+
return NextResponse.json({ alreadyInTrial: true });
44+
}
45+
}
46+
47+
// Create new trial record
48+
const trialEnd = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);
49+
await db.insert(schema.subscription).values({
50+
id: randomUUID(),
51+
plan: 'snowleopard',
52+
referenceId: userId,
53+
status: 'trialing',
54+
trialStart: now,
55+
trialEnd,
56+
});
57+
58+
return NextResponse.json({ trialEnd });
59+
}

apps/snow-leopard/app/api/user/subscription-status/route.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,25 @@ export async function GET() {
1616

1717
if (process.env.STRIPE_ENABLED === 'true') {
1818
const subscription = await getActiveSubscriptionByUserId({ userId: session.user.id });
19-
hasActiveSubscription = subscription?.status === 'active' || subscription?.status === 'trialing';
19+
if (subscription) {
20+
if (subscription.status === 'active') {
21+
hasActiveSubscription = true;
22+
} else if (
23+
subscription.status === 'trialing' &&
24+
subscription.trialEnd &&
25+
new Date(subscription.trialEnd) > new Date()
26+
) {
27+
hasActiveSubscription = true;
28+
} else {
29+
hasActiveSubscription = false;
30+
}
31+
} else {
32+
hasActiveSubscription = false;
33+
}
2034
console.log(`[api/user/subscription-status] User: ${session.user.id}, Sub Status: ${subscription?.status}, HasActive: ${hasActiveSubscription}`);
2135
} else {
2236
console.log(`[api/user/subscription-status] Stripe DISABLED, granting access.`);
37+
hasActiveSubscription = true;
2338
}
2439

2540
return NextResponse.json({ hasActiveSubscription });
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NextResponse } from 'next/server';
2+
import { getSession } from '@/app/(auth)/auth';
3+
import { getActiveSubscriptionByUserId } from '@/lib/db/queries';
4+
5+
export const dynamic = 'force-dynamic';
6+
7+
export async function GET() {
8+
try {
9+
const session = await getSession();
10+
if (!session?.user?.id) {
11+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
12+
}
13+
14+
const subscription = await getActiveSubscriptionByUserId({ userId: session.user.id });
15+
return NextResponse.json({ subscription });
16+
} catch (error) {
17+
console.error('[api/user/subscription] Error fetching subscription:', error);
18+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
19+
}
20+
}

apps/snow-leopard/app/documents/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function DocumentsLayout({ children }: { children: ReactNode }) {
2020
const { data: subscriptionData, isLoading: isSubscriptionLoading } = useSWR(
2121
shouldFetchSubscription ? '/api/user/subscription-status' : null,
2222
fetcher,
23-
{ revalidateOnFocus: false }
23+
{ revalidateOnFocus: false, refreshInterval: 60000 }
2424
);
2525

2626
useEffect(() => {

0 commit comments

Comments
 (0)