Skip to content

Commit 2a81fd7

Browse files
committed
Adds Generate New Prompt button.
1 parent c9cb558 commit 2a81fd7

5 files changed

Lines changed: 166 additions & 42 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use server";
2+
import { requireAuth } from "../auth";
3+
import { prisma } from "@repo/prisma";
4+
5+
const PROMPTS = [
6+
"What are your goals for this week?",
7+
"What challenges did you face last week and how did you overcome them?",
8+
"What are you grateful for today?",
9+
"What is one thing you want to improve on this week?",
10+
"What was a recent success you had and how did it make you feel?",
11+
"What is something new you want to try this week?",
12+
"How do you plan to take care of yourself this week?",
13+
"What is a positive affirmation you can repeat to yourself this week?",
14+
"What is one thing you can do to make someone else's day better this week?",
15+
"What is a lesson you learned recently that you want to remember?"
16+
];
17+
18+
export async function generatePrompt() {
19+
const { userId } = await requireAuth();
20+
21+
if (!userId) {
22+
throw new Error("Unauthorized");
23+
}
24+
25+
try {
26+
const newPrompt = await prisma.prompt.create({
27+
data: {
28+
userId,
29+
content: PROMPTS[Math.floor(Math.random() * PROMPTS.length - 1)] || "What are your goals for this week?",
30+
frequency: 'weekly',
31+
},
32+
});
33+
34+
return { success: true, prompt: newPrompt };
35+
} catch (error) {
36+
console.error("Error generating new prompt:", error);
37+
return { success: false, message: "Failed to generate new prompt." };
38+
}
39+
}
40+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use client';
2+
import { useState } from 'react';
3+
import { generatePrompt } from '../actions/prompt/generatePrompt';
4+
5+
const GenerateNewPrompt = () => {
6+
const [isLoading, setIsLoading] = useState(false);
7+
return (
8+
<button
9+
className="inline-block px-6 py-3 mt-2 text-white font-medium rounded-lg transition-all duration-200"
10+
style={{
11+
background:
12+
'linear-gradient(135deg, var(--ps-primary-500) 0%, var(--ps-primary-600) 100%)',
13+
}}
14+
onClick={async () => {
15+
setIsLoading(true);
16+
try {
17+
const response = await generatePrompt();
18+
if (response.success) {
19+
location.reload(); // Refresh the page to load new prompts
20+
} else {
21+
alert('Failed to generate prompt: ' + response.message);
22+
}
23+
} catch (error) {
24+
console.error('Error calling generatePrompt:', error);
25+
alert('An unexpected error occurred.');
26+
} finally {
27+
setIsLoading(false);
28+
}
29+
}}
30+
disabled={isLoading}
31+
>
32+
{isLoading ? 'Generating...' : 'Generate New Prompt'}
33+
</button>
34+
35+
)
36+
37+
}
38+
39+
export default GenerateNewPrompt;

apps/web/app/prompt/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { redirect } from 'next/navigation';
22
import Link from 'next/link';
33
import { requireAuth } from '../actions/auth';
44
import { fetchUserPrompts } from '../actions/prompt';
5+
import GenerateNewPrompt from '../components/GenerateNewPrompt';
56

67
type Prompt = {
78
id: string;
@@ -30,6 +31,7 @@ export default async function Prompt() {
3031
You may still respond to any open prompts. Click on any prompt to
3132
view details or write your entry.
3233
</p>
34+
<GenerateNewPrompt />
3335
</div>
3436
<div className="flex flex-col gap-2 space-y-4">
3537
{prompts?.length > 0 ? (
@@ -49,9 +51,8 @@ export default async function Prompt() {
4951
</div>
5052
<div className="flex items-start justify-between mb-3">
5153
<span
52-
className={`px-3 py-1 rounded-full text-xs font-medium ${
53-
prompt.isOpen ? 'text-white' : 'text-ps-secondary'
54-
}`}
54+
className={`px-3 py-1 rounded-full text-xs font-medium ${prompt.isOpen ? 'text-white' : 'text-ps-secondary'
55+
}`}
5556
style={{
5657
backgroundColor: prompt.isOpen
5758
? 'var(--ps-secondary-500)'

docs/GENERATE_NEW_PROMPT_PLAN.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Plan to Add "Generate New Prompt" Button to `/prompt` Page
2+
3+
## Objective
4+
Add a "Generate New Prompt" button to the `/prompt` page. When clicked, this button calls a Next.js action to generate a new prompt. The action should not send an email but should refresh the `/prompt` page to display the new, un-responded-to prompt in the list.
5+
6+
---
7+
8+
## Task List
9+
10+
### Backend
11+
1. **Create Prompt Generation Action**
12+
- Implement a server-side Next.js action to generate a new prompt.
13+
- The action should:
14+
- Authenticate the user (reuse `requireAuth` logic).
15+
- Generate and save a new prompt in the database.
16+
- Return success or failure status.
17+
18+
### Frontend
19+
2. **Create Frontend Button Component**
20+
- Add a "Generate New Prompt" button to the `/prompt` page.
21+
- Style the button to align with existing design.
22+
23+
3. **Integrate Button with Action**
24+
- Add `onClick` logic to call the `generatePrompt` Next.js action.
25+
- Use `fetch` or `Next.js` server action invocation tools.
26+
- Add a loading state to the button while the action executes.
27+
28+
4. **Refresh Page on Success**
29+
- On successful prompt creation, reload the `/prompt` page to reflect the new prompt in the list.
30+
31+
### Validation
32+
5. **Test Functionality**
33+
- Verify the button generates new prompts correctly without errors.
34+
- Ensure the new prompt appears immediately after page reload.
35+
- Test edge cases (e.g., network failure, backend errors).
36+
37+
### Documentation
38+
6. **Update Documentation**
39+
- Document the new action and UI logic for future maintainability.
40+
41+
---
42+
43+
## Deliverable
44+
Upon completion, the `/prompt` page will feature a working "Generate New Prompt" button. Clicking it will generate a new prompt and display it in the list without sending any emails.
45+

packages/prisma/schema.prisma

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,57 +11,57 @@ generator client {
1111
}
1212

1313
model User {
14-
id String @id @default(uuid())
15-
email String @unique
16-
firstName String?
17-
lastName String?
18-
phone String?
19-
14+
id String @id @default(uuid())
15+
email String @unique
16+
firstName String?
17+
lastName String?
18+
phone String?
19+
2020
// Billing Address (for tax calculation only)
21-
addressLine1 String?
22-
addressLine2 String?
23-
addressCity String?
24-
addressState String?
21+
addressLine1 String?
22+
addressLine2 String?
23+
addressCity String?
24+
addressState String?
2525
addressPostalCode String?
26-
addressCountry String? @default("US")
27-
26+
addressCountry String? @default("US")
27+
2828
// Stripe Integration - ONLY references, never payment data
29-
stripeCustomerId String? @unique
30-
31-
frequency String // e.g., 'daily', 'weekly'
32-
createdAt DateTime @default(now())
33-
updatedAt DateTime @updatedAt
34-
35-
prompts Prompt[]
36-
entries Entry[]
37-
subscriptions Subscription[]
38-
refreshTokens RefreshToken[]
29+
stripeCustomerId String? @unique
30+
31+
frequency String // e.g., 'daily', 'weekly'
32+
createdAt DateTime @default(now())
33+
updatedAt DateTime @updatedAt
34+
35+
prompts Prompt[]
36+
entries Entry[]
37+
subscriptions Subscription[]
38+
refreshTokens RefreshToken[]
3939
4040
@@map("users")
4141
}
4242

4343
model Subscription {
44-
id String @id @default(uuid())
45-
userId String
46-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
47-
44+
id String @id @default(uuid())
45+
userId String
46+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
47+
4848
// Stripe references - never store actual payment data
4949
stripeSubscriptionId String @unique
5050
status SubscriptionStatus
5151
planType PlanType
52-
52+
5353
// Subscription timing
54-
currentPeriodStart DateTime
55-
currentPeriodEnd DateTime
56-
cancelAtPeriodEnd Boolean @default(false)
57-
canceledAt DateTime?
58-
54+
currentPeriodStart DateTime
55+
currentPeriodEnd DateTime
56+
cancelAtPeriodEnd Boolean @default(false)
57+
canceledAt DateTime?
58+
5959
// Pricing reference (Stripe Price ID)
60-
stripePriceId String
61-
62-
createdAt DateTime @default(now())
63-
updatedAt DateTime @updatedAt
64-
60+
stripePriceId String
61+
62+
createdAt DateTime @default(now())
63+
updatedAt DateTime @updatedAt
64+
6565
@@index([userId])
6666
@@index([stripeSubscriptionId])
6767
@@map("subscriptions")
@@ -72,7 +72,6 @@ model Prompt {
7272
content String
7373
createdAt DateTime @default(now())
7474
sentAt DateTime?
75-
frequency String // e.g., 'daily', 'weekly'
7675
userId String
7776
user User @relation(fields: [userId], references: [id])
7877
entries Entry[]
@@ -102,10 +101,10 @@ model RefreshToken {
102101
expiresAt DateTime
103102
createdAt DateTime @default(now())
104103
105-
@@map("refresh_tokens")
106104
@@index([userId])
107105
@@index([token])
108106
@@index([expiresAt])
107+
@@map("refresh_tokens")
109108
}
110109

111110
// Enums for subscription management

0 commit comments

Comments
 (0)