
Table of Contents
- The Problem
- Solution Overview
- Technical Architecture
- Core Features
- AI Integration Challenges
- Stripe Integration
- Advanced UI Components
- Deployment
- Lessons Learned
- Conclusion
The Problem
As a developer, I noticed a common struggle: how to effectively showcase daily work and expertise on social media. Many developers have valuable insights from their work but struggle to transform technical content into engaging posts.
Solution Overview
LogToPost transforms daily work logs into engaging X (Twitter) posts using AI-powered content generation:
- Daily Logging: Users write about their work activities
- Text Selection: Select specific portions for transformation
- AI Generation: GPT-4 converts content into engaging posts
- Content Management: Review, edit, and manage generated posts
Technical Architecture
Frontend Stack
- Next.js 15 with App Router
- TypeScript for type safety
- Tailwind CSS for styling
- Framer Motion for animations
- Radix UI for accessible components
Backend Stack
- Express.js with TypeScript
- Drizzle ORM for database operations
- PostgreSQL as primary database
- JWT Authentication for sessions
- OpenAI API for content generation
- Stripe for subscription billing
Infrastructure
- Hetzner for hosting
- PM2 for process management
- GitHub Actions for CI/CD
Core Features
AI-Powered Content Generation
export async function generatePosts(
selectedText: string,
userPrompt: string = "",
userId: string
): Promise<GeneratedPost[]> {
const sanitizedPrompt = sanitizeUserPrompt(userPrompt);
const systemPrompt = `You are an expert social media content creator...`;
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: fullPrompt }],
temperature: 0.7,
max_tokens: 1000,
});
return parseGeneratedContent(completion.choices[0].message.content);
}
Key Features:
- Prompt injection prevention
- Context preservation
- Multiple post variations
- Robust error handling
Subscription System
export async function getSubscriptionStatus(userId: string) {
const user = await db.select().from(users).where(eq(users.id, userId));
const subscriptions = await stripe.subscriptions.list({
customer: user[0].stripeCustomerId,
status: 'active',
limit: 1,
});
if (activeSubscription) {
const usage = await getCurrentMonthUsage(userId);
return {
status: 'premium',
generationsLeft: 100 - usage,
renewsAt: new Date(activeSubscription.current_period_end * 1000),
};
}
return { status: 'free', generationsLeft: 0 };
}
Billing Features:
- £9.99/month for 100 AI generations
- Real-time usage tracking
- Billing portal integration
- Admin unlimited access
AI Integration Challenges
1. Prompt Injection Prevention
function sanitizeUserPrompt(userPrompt: string): string {
const dangerousPatterns = [
/ignore\s+(previous|above|all)\s+instructions?/gi,
/system\s*:/gi,
/assistant\s*:/gi,
];
let sanitized = userPrompt;
dangerousPatterns.forEach(pattern => {
sanitized = sanitized.replace(pattern, '');
});
return sanitized.slice(0, 500);
}
2. Response Validation
function parseGeneratedContent(response: string): GeneratedPost[] {
try {
const parsed = JSON.parse(response);
if (Array.isArray(parsed.posts)) {
return parsed.posts.map(validatePost).filter(Boolean);
}
} catch {
return extractPostsFromText(response);
}
throw new Error('Unable to parse AI response');
}
Stripe Integration
Real-time billing without webhooks for immediate subscription updates:
export async function createSubscription(userId: string) {
const user = await getUser(userId);
let customerId = user.stripeCustomerId;
if (!customerId) {
const customer = await stripe.customers.create({
email: user.email,
metadata: { userId: userId },
});
customerId = customer.id;
await updateUser(userId, { stripeCustomerId: customerId });
}
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: process.env.STRIPE_PRICE_ID }],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent'],
});
return {
subscriptionId: subscription.id,
clientSecret: subscription.latest_invoice.payment_intent.client_secret,
};
}
Advanced UI Components
Morphing Text Animation
export const MorphingText = ({ texts }: { texts: string[] }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
const interval = setInterval(() => {
setIsAnimating(true);
setTimeout(() => {
setCurrentIndex((prev) => (prev + 1) % texts.length);
setIsAnimating(false);
}, 300);
}, 3000);
return () => clearInterval(interval);
}, [texts.length]);
return (
<motion.span
key={currentIndex}
initial={{ opacity: 0, filter: 'blur(10px)' }}
animate={{
opacity: isAnimating ? 0 : 1,
filter: isAnimating ? 'blur(10px)' : 'blur(0px)'
}}
transition={{ duration: 0.3 }}
className="gradient-text"
>
{texts[currentIndex]}
</motion.span>
);
};
UI Features:
- Morphing text with blur effects
- Gradient text animations
- Smooth cursor tracking
- Dark/light theme support
- Keyboard shortcuts
Text Selection Preservation
const useTextSelection = () => {
const [selection, setSelection] = useState<{
text: string;
range: Range | null;
}>({ text: '', range: null });
useEffect(() => {
const handleSelection = () => {
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
const range = sel.getRangeAt(0);
const text = sel.toString().trim();
if (text.length > 0) {
setSelection({ text, range });
}
}
};
document.addEventListener('selectionchange', handleSelection);
return () => document.removeEventListener('selectionchange', handleSelection);
}, []);
return selection;
};
Deployment
Modern CI/CD with GitHub Actions and zero-downtime deployments:
- Automated deployment to Hetzner servers
- PM2 process management for zero downtime
- SSL automation with Let's Encrypt
- Database migrations with rollback capability
- Environment variable management
Lessons Learned
What Worked Well
- Direct Stripe Integration: Simplified architecture without webhooks
- Migration-Driven Development: Iterative database evolution
- TypeScript Everywhere: Improved development speed and reliability
- Custom UI Components: Polished user experience with Framer Motion
Challenges Overcome
- AI Prompt Security: Robust sanitisation without breaking functionality
- Real-time Billing: Immediate updates without webhook complexity
- State Management: Balancing optimistic updates with data integrity
- Performance: Making AI generation feel instant despite API latency
Future Improvements
- Multi-Platform Support: LinkedIn, Instagram content generation
- Team Collaboration: Shared logs and approval workflows
- Content Calendar: Scheduling and automated posting
- Analytics Dashboard: Performance tracking and insights
Conclusion
LogToPost demonstrates modern SaaS development with:
- Secure AI integration with prompt injection prevention
- Real-time billing without webhook complexity
- Advanced UI components with smooth animations
- Professional deployment pipeline
The project showcases how thoughtful architecture and user experience focus can create a compelling product that solves real problems for developers and content creators.
Building LogToPost highlighted the importance of:
- Starting simple and evolving based on user needs
- Prioritising security from day one
- Focusing on performance for delightful UX
- Building scalable foundations
For developers interested in AI-powered SaaS applications, LogToPost serves as a comprehensive example of modern full-stack development with subscription billing and professional UI design.
LogToPost continues helping developers transform daily work into engaging social media content, with a technical foundation that supports rapid feature development while maintaining professional reliability.