diff --git a/.dockerignore b/.dockerignore index 1f869dc..3736b6a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,7 +15,7 @@ coverage # Environment files .env .env.* -!.env.example + # Git .git diff --git a/README.md b/README.md index d51d265..8037500 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,35 @@ Then run: docker-compose up -d ``` +# Newsletter Management + +## Quick Start + +1. Visit `/admin/newsletter` (team members only) +2. Fill in the newsletter form +3. Click "Generate Code" and "Copy Code" +4. Open `data/newsletters.ts` +5. Paste the code at the start of the `newsletters` array +6. Save and deploy + +That's it! ✨ + +## Example + +The admin form generates this: +```typescript +{ + id: "18", + slug: "january-2025-updates", + title: "January 2025 Updates", + content: `...`, + publishedAt: new Date("2025-01-17"), + author: "opensox.ai team", +}, +``` + +Just paste it into the newsletters array! + ## Our contributors diff --git a/apps/api/.env.example b/apps/api/.env.example deleted file mode 100644 index 12e24a4..0000000 --- a/apps/api/.env.example +++ /dev/null @@ -1,32 +0,0 @@ -# Note: pls don't use values as strings for the below variables, if you wanna run using docker becuase in this case, docker build fails. -# simply use a format like this: name-of-the-variable=variable-value -# for example: PORT=8080 - -# Required -CORS_ORIGINS=http://localhost:3000 -NODE_ENV=development -PORT=8080 - -DATABASE_URL=postgresql://USER:PASSWORD@localhost:5432/your_database_name?schema=public -JWT_SECRET=replace-with-a-strong-random-secret - -# compulsory for the project search tool -# Get a PAT from here https://github.com/settings/developers -GITHUB_PERSONAL_ACCESS_TOKEN=token-from-your-github-account - -# for payments -RAZORPAY_KEY_ID=razorpay-key-id -RAZORPAY_KEY_SECRET=razorpay-key-secret -RAZORPAY_WEBHOOK_SECRET=razorpay-webhook-secret - -# to send slack invite. -# it'll be similar to : https://join.slack.com/t/name-of-ur-org/shared_invite/invite-id -SLACK_INVITE_URL=slack-invite-url - -# to send the email (transactional) -# get one from here: https://www.zoho.com/zeptomail/pricing.html?src=pd-menu -ZEPTOMAIL_TOKEN=zeptomail-token - -# key for the encryption -# can be created by running this: echo "$(openssl rand -hex 32)opensox$(openssl rand -hex 16)" -ENCRYPTION_KEY=encryption-key diff --git a/apps/api/src/clients/razorpay.ts b/apps/api/src/clients/razorpay.ts index 25a7f81..639a740 100644 --- a/apps/api/src/clients/razorpay.ts +++ b/apps/api/src/clients/razorpay.ts @@ -1,21 +1,35 @@ import Razorpay from "razorpay"; -const RAZORPAY_KEY_ID = process.env.RAZORPAY_KEY_ID; -const RAZORPAY_KEY_SECRET = process.env.RAZORPAY_KEY_SECRET; +let rz_instance: Razorpay | null = null; -if (!RAZORPAY_KEY_ID) { - throw new Error( - "RAZORPAY_KEY_ID is required but not set in environment variables. Please configure it in your .env file." - ); -} +/** + * Get Razorpay instance (lazy initialization) + * Only initializes when actually needed, allowing the app to start without Razorpay config + */ +export function getRazorpayInstance(): Razorpay { + if (rz_instance) { + return rz_instance; + } -if (!RAZORPAY_KEY_SECRET) { - throw new Error( - "RAZORPAY_KEY_SECRET is required but not set in environment variables. Please configure it in your .env file." - ); -} + const RAZORPAY_KEY_ID = process.env.RAZORPAY_KEY_ID; + const RAZORPAY_KEY_SECRET = process.env.RAZORPAY_KEY_SECRET; + + if (!RAZORPAY_KEY_ID) { + throw new Error( + "RAZORPAY_KEY_ID is required but not set in environment variables. Please configure it in your .env file." + ); + } -export const rz_instance = new Razorpay({ - key_id: RAZORPAY_KEY_ID, - key_secret: RAZORPAY_KEY_SECRET, -}); + if (!RAZORPAY_KEY_SECRET) { + throw new Error( + "RAZORPAY_KEY_SECRET is required but not set in environment variables. Please configure it in your .env file." + ); + } + + rz_instance = new Razorpay({ + key_id: RAZORPAY_KEY_ID, + key_secret: RAZORPAY_KEY_SECRET, + }); + + return rz_instance; +} diff --git a/apps/api/src/services/payment.service.ts b/apps/api/src/services/payment.service.ts index 85afa82..d906f07 100644 --- a/apps/api/src/services/payment.service.ts +++ b/apps/api/src/services/payment.service.ts @@ -1,4 +1,4 @@ -import { rz_instance } from "../clients/razorpay.js"; +import { getRazorpayInstance } from "../clients/razorpay.js"; import crypto from "crypto"; import prismaModule from "../prisma.js"; import { @@ -74,6 +74,7 @@ export const paymentService = { const { amount, currency, receipt, notes } = input; try { + const rz_instance = getRazorpayInstance(); const order = await rz_instance.orders.create({ amount, currency, diff --git a/apps/web/package.json b/apps/web/package.json index 77494fc..16b9e53 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -34,8 +34,10 @@ "posthog-js": "^1.203.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", "react-qr-code": "^2.0.18", "react-tweet": "^3.2.1", + "remark-gfm": "^4.0.1", "superjson": "^2.2.5", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", diff --git a/apps/web/src/app/(main)/admin/newsletter/_components/NewsAdmin.tsx b/apps/web/src/app/(main)/admin/newsletter/_components/NewsAdmin.tsx new file mode 100644 index 0000000..08c2d39 --- /dev/null +++ b/apps/web/src/app/(main)/admin/newsletter/_components/NewsAdmin.tsx @@ -0,0 +1,232 @@ +"use client"; +import { useState } from 'react'; + +export default function NewsletterAdmin() { + const [formData, setFormData] = useState({ + title: '', + description: '', + content: '', + author: 'opensox.ai team', + issueNumber: '', + readTime: '' + }); + + const [generatedCode, setGeneratedCode] = useState(''); + const [copied, setCopied] = useState(false); + + const generateSlug = (title: string) => { + return title + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, ''); + }; + + const getNextId = () => { + return "18"; // Update this to the next ID number + }; + + const generateCode = () => { + if (!formData.title || !formData.content) { + alert('Please fill in Title and Content'); + return; + } + + const slug = generateSlug(formData.title); + const today = new Date().toISOString().split('T')[0]; + + const code = `{ + id: "${getNextId()}", + slug: "${slug}", + title: "${formData.title}",${formData.description ? ` + description: "${formData.description}",` : ''} + content: \`${formData.content}\`, + publishedAt: new Date("${today}"), + author: "${formData.author}",${formData.issueNumber ? ` + issueNumber: "${formData.issueNumber}",` : ''}${formData.readTime ? ` + readTime: ${formData.readTime},` : ''} +},`; + + setGeneratedCode(code); + }; + + const copyToClipboard = () => { + navigator.clipboard.writeText(generatedCode); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleReset = () => { + setFormData({ + title: '', + description: '', + content: '', + author: 'opensox.ai team', + issueNumber: '', + readTime: '' + }); + setGeneratedCode(''); + setCopied(false); + }; + + return ( +
+
+
+

+ Add New Newsletter +

+

Fill in the details below, then copy the generated code to newsletters.ts

+
+ +
+ {/* Form Section */} +
+

Newsletter Details

+ +
+
+ + setFormData({...formData, title: e.target.value})} + className="w-full px-4 py-2 bg-gray-950 border border-gray-700 rounded-lg focus:border-purple-500 focus:outline-none" + placeholder="January 2025 Updates" + /> +
+ +
+ + setFormData({...formData, description: e.target.value})} + className="w-full px-4 py-2 bg-gray-950 border border-gray-700 rounded-lg focus:border-purple-500 focus:outline-none" + placeholder="Brief summary of the newsletter" + /> +
+ +
+ +