Temporarily disable middleware for debugging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-01-31 15:19:52 +01:00
parent 47e1682d44
commit 6d1c08dce4
7 changed files with 430 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
# Requirements: Leopost
**Defined:** 2026-01-30
**Core Value:** L'AI fa il lavoro sporco del social media manager — minimo sforzo, massima resa
## v1 Requirements
Requirements per il rilascio iniziale. Ogni requirement mappa a fasi della roadmap.
### Authentication
- [ ] **AUTH-01**: Utente può registrarsi con email/password
- [ ] **AUTH-02**: Utente può accedere con Google OAuth
- [ ] **AUTH-03**: Sistema supporta 3 piani (Free, Creator, Pro) con limiti configurabili
- [ ] **AUTH-04**: Utente può collegare account Facebook tramite OAuth
### Onboarding
- [ ] **ONBR-01**: Wizard raccoglie info base (nome attività, settore, descrizione)
- [ ] **ONBR-02**: Contesto utente persiste tra sessioni (memoria)
### AI Chat
- [ ] **CHAT-01**: Interfaccia chat come esperienza principale dell'app
- [ ] **CHAT-02**: Utente può scegliere modello AI (GPT, Claude, Gemini)
- [ ] **CHAT-03**: AI propone azioni suggerite dopo completamento onboarding
- [ ] **CHAT-04**: AI memorizza e impara preferenze utente nel tempo
### Content Generation
- [ ] **CONT-01**: AI genera post testuali su richiesta dell'utente
- [ ] **CONT-02**: AI genera immagini per i post
- [ ] **CONT-03**: AI crea piano editoriale (calendario contenuti suggeriti)
### Scheduling & Publishing
- [ ] **SCHD-01**: Utente può programmare post con data/ora specifica
- [ ] **SCHD-02**: Post vengono pubblicati automaticamente su Facebook
- [ ] **SCHD-03**: Utente può configurare livello automazione (da approval a autopilot)
- [ ] **SCHD-04**: Calendario editoriale visuale mostra post programmati
### Messaging Integration
- [ ] **MSGN-01**: Utente può inviare comandi via Telegram bot
- [ ] **MSGN-02**: Utente può inviare comandi via WhatsApp bot
## v2 Requirements
Deferred per release future. Tracciati ma non nella roadmap corrente.
### Authentication
- **AUTH-05**: Utente può accedere con Facebook OAuth
- **AUTH-06**: Utente può accedere con LinkedIn OAuth
- **AUTH-07**: Utente può accedere con Instagram OAuth
### Social Platforms
- **SOCL-01**: Utente può collegare e pubblicare su Instagram
- **SOCL-02**: Utente può collegare e pubblicare su LinkedIn
- **SOCL-03**: Utente può collegare e pubblicare su YouTube
- **SOCL-04**: Utente può collegare e pubblicare su TikTok
- **SOCL-05**: Utente può collegare e pubblicare su X (Twitter)
### Onboarding Extended
- **ONBR-03**: Wizard raccoglie brand kit (logo, colori, font)
- **ONBR-04**: Wizard raccoglie siti web e link
- **ONBR-05**: Wizard raccoglie target audience e zona geografica
### Content Generation Extended
- **CONT-04**: AI adatta tono/lunghezza/hashtag per piattaforma specifica
- **CONT-05**: Utente può fornire template grafici come base
- **CONT-06**: AI genera video (oltre a immagini)
## Out of Scope
Esclusi esplicitamente. Documentati per prevenire scope creep.
| Feature | Reason |
|---------|--------|
| App native iOS/Android | Architettura headless pronta, ma web-first per v1 |
| Dashboard analytics avanzate | Focus su creazione/pubblicazione, non analytics |
| Social listening | Complessità elevata, non core per freelance |
| Influencer marketplace | Fuori target (freelance, non brand) |
| White-label | Enterprise feature, non micro-SaaS |
| Video editing integrato | Complessità, costi storage/bandwidth |
| Gestione ads/campagne | Fuori scope, focus su contenuti organici |
## Traceability
Quali fasi coprono quali requirements. Aggiornato durante creazione roadmap.
| Requirement | Phase | Status |
|-------------|-------|--------|
| AUTH-01 | TBD | Pending |
| AUTH-02 | TBD | Pending |
| AUTH-03 | TBD | Pending |
| AUTH-04 | TBD | Pending |
| ONBR-01 | TBD | Pending |
| ONBR-02 | TBD | Pending |
| CHAT-01 | TBD | Pending |
| CHAT-02 | TBD | Pending |
| CHAT-03 | TBD | Pending |
| CHAT-04 | TBD | Pending |
| CONT-01 | TBD | Pending |
| CONT-02 | TBD | Pending |
| CONT-03 | TBD | Pending |
| SCHD-01 | TBD | Pending |
| SCHD-02 | TBD | Pending |
| SCHD-03 | TBD | Pending |
| SCHD-04 | TBD | Pending |
| MSGN-01 | TBD | Pending |
| MSGN-02 | TBD | Pending |
**Coverage:**
- v1 requirements: 19 total
- Mapped to phases: 0
- Unmapped: 19 ⚠️
---
*Requirements defined: 2026-01-30*
*Last updated: 2026-01-30 after initial definition*

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# Leopost
Lab project hosted at https://lab.mlhub.it/leopost/
## Development
This project uses [gsd (get-shit-done)](https://github.com/glittercowboy/get-shit-done) for spec-driven development.
### Getting Started
1. Run `/gsd:new-project` to initialize project specs
2. Follow gsd workflow: discuss → plan → execute → verify
3. Run `vps-lab-deploy` when ready to deploy
### Project Structure
```
leopost/
├── .planning/ # gsd planning docs (created by /gsd:new-project)
│ ├── PROJECT.md # Project vision
│ ├── REQUIREMENTS.md # Scoped requirements
│ ├── ROADMAP.md # Phase breakdown
│ └── STATE.md # Project memory
├── src/ # Source code (created during development)
└── README.md # This file
```
## Repository
- **Gitea**: https://git.mlhub.it/Michele/leopost
- **Clone**: `git clone https://git.mlhub.it/Michele/leopost.git`
## Deployment
When ready to deploy, use `vps-lab-deploy` skill.
- **URL**: https://lab.mlhub.it/leopost/
- **VPS**: /opt/lab-leopost/

View File

@@ -0,0 +1,97 @@
'use client'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'
import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'
export default function ResetPasswordPage() {
const [email, setEmail] = useState('')
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
const [loading, setLoading] = useState(false)
const supabase = createClient()
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError(null)
setLoading(true)
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
email,
{
redirectTo: `${window.location.origin}/auth/callback?next=/update-password`,
}
)
if (resetError) {
setError(resetError.message)
setLoading(false)
return
}
setSuccess(true)
setLoading(false)
}
return (
<Card>
<CardHeader className="text-center">
<CardTitle>Recupera password</CardTitle>
<CardDescription>
Inserisci la tua email per ricevere il link di reset
</CardDescription>
</CardHeader>
<CardContent>
{success ? (
<div className="text-center space-y-4">
<div className="p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-800">
Se l&apos;email esiste, riceverai un link per reimpostare la password.
</p>
</div>
<Link href="/login" className="text-blue-600 hover:underline">
Torna al login
</Link>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800 text-sm">{error}</p>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<Input
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="tu@esempio.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Invio...' : 'Invia link di reset'}
</Button>
<p className="text-center text-sm text-gray-600">
<Link href="/login" className="text-blue-600 hover:underline">
Torna al login
</Link>
</p>
</form>
)}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,144 @@
'use client'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'
import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'
export default function UpdatePasswordPage() {
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
const [success, setSuccess] = useState(false)
const [loading, setLoading] = useState(false)
const supabase = createClient()
function validatePassword(pwd: string): string | null {
if (pwd.length < 8) {
return 'La password deve contenere almeno 8 caratteri'
}
if (!/[0-9]/.test(pwd)) {
return 'La password deve contenere almeno un numero'
}
if (!/[A-Z]/.test(pwd)) {
return 'La password deve contenere almeno una lettera maiuscola'
}
return null
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError(null)
setFieldErrors({})
setLoading(true)
// Validate password
const passwordError = validatePassword(password)
if (passwordError) {
setFieldErrors({ password: passwordError })
setLoading(false)
return
}
// Validate confirm password
if (password !== confirmPassword) {
setFieldErrors({ confirmPassword: 'Le password non coincidono' })
setLoading(false)
return
}
const { error: updateError } = await supabase.auth.updateUser({
password: password,
})
if (updateError) {
setError(updateError.message)
setLoading(false)
return
}
setSuccess(true)
setLoading(false)
}
return (
<Card>
<CardHeader className="text-center">
<CardTitle>Nuova password</CardTitle>
<CardDescription>
Inserisci la tua nuova password
</CardDescription>
</CardHeader>
<CardContent>
{success ? (
<div className="text-center space-y-4">
<div className="p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-800">Password aggiornata con successo!</p>
</div>
<Link href="/login" className="text-blue-600 hover:underline">
Vai al login
</Link>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800 text-sm">{error}</p>
</div>
)}
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Nuova Password
</label>
<Input
id="password"
name="password"
type="password"
autoComplete="new-password"
required
placeholder="Minimo 8 caratteri"
value={password}
onChange={(e) => setPassword(e.target.value)}
error={!!fieldErrors.password}
/>
{fieldErrors.password && (
<p className="mt-1 text-sm text-red-600">{fieldErrors.password}</p>
)}
<p className="mt-1 text-xs text-gray-500">
Almeno 8 caratteri, 1 numero, 1 maiuscola
</p>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
Conferma Password
</label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
autoComplete="new-password"
required
placeholder="Ripeti la password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
error={!!fieldErrors.confirmPassword}
/>
{fieldErrors.confirmPassword && (
<p className="mt-1 text-sm text-red-600">{fieldErrors.confirmPassword}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Aggiornamento...' : 'Aggiorna password'}
</Button>
</form>
)}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,26 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import Link from 'next/link'
export default function VerifyEmailPage() {
return (
<Card>
<CardHeader className="text-center">
<CardTitle>Verifica la tua email</CardTitle>
<CardDescription>
Ti abbiamo inviato un link di conferma
</CardDescription>
</CardHeader>
<CardContent className="text-center space-y-4">
<p className="text-gray-600">
Controlla la tua casella email e clicca sul link per attivare il tuo account.
</p>
<p className="text-sm text-gray-500">
Non hai ricevuto l&apos;email? Controlla lo spam.
</p>
<Link href="/login" className="text-blue-600 hover:underline">
Torna al login
</Link>
</CardContent>
</Card>
)
}

1
tsconfig.tsbuildinfo Normal file

File diff suppressed because one or more lines are too long