ZeroToShip

Docs/Tutorial

// tutorial

Privacy policy generata con GPT

Stripe non ti approva un account senza termini e privacy policy raggiungibili. Scriverli da zero è mezza giornata persa; farseli buttare giù da un modello e poi rivederli sono venti minuti. In ZeroToShip il prompt è già scritto: sta come commento in cima a app/privacy-policy/page.tsx e app/tos/page.tsx.

1. Il prompt è già nel file

Apri app/privacy-policy/page.tsx: prima dell'import del componente trovi questo blocco. È un prompt completo, con i dati di ZeroToShip dentro. Il tuo lavoro è sostituire quei dati con i tuoi.

app/privacy-policy/page.tsx
tsx
// You are an excellent lawyer.

// I need your help to write a simple privacy policy for my website. Here is some context:
// - Website: https://zerotoship.com
// - Name: ZeroToShip
// - Description: A JavaScript code boilerplate to help entrepreneurs launch their startups faster
// - User data collected: name, email and payment information
// - Non-personal data collection: web cookies
// - Purpose of Data Collection: Order processing
// - Data sharing: we do not share the data with any other parties
// - Children's Privacy: we do not collect any data from children
// - Updates to the Privacy Policy: users will be updated by email
// - Contact information: alberto@zerotoship.com

// Please write a simple privacy policy for my site. Add the current date. Do not add or explain your reasoning. Answer:

Le due righe finali sono la parte che la gente dimentica: add the current date e do not add or explain your reasoning. Senza, ti ritrovi mezza risposta di preamboli da incollare a mano.

2. Prendi i fatti dal tuo config.ts

Il prompt vale quanto valgono i dati che gli dai. Quasi tutti stanno già in config.ts, non inventarli:

config.ts
ts
appName: "ZeroToShip",
domainName: "zerotoship.com",          // niente https://, niente slash finale
resend: {
  supportEmail: "...",                 // l'indirizzo di contatto della policy
},
stripe: {
  plans: [ /* Starter 199, All-In 249 — pagamento una tantum */ ],
},

3. Elenca i fornitori che usi davvero

Questa è la parte che il prompt di default lascia vaga e che invece va resa esplicita, perché sono i soggetti a cui passano dati. In un progetto ZeroToShip non toccato sono questi:

  • Supabase — autenticazione (magic link e login Google) e database. Ci finiscono email, id utente e la riga di profilo con lo stato di accesso.
  • Stripe — checkout e abbonamenti. I dati della carta non passano mai dal tuo server: l'utente li inserisce su Stripe. Tu ti tieni un customer_id e un price_id.
  • Resend — email transazionali, e in molti setup anche SMTP per i magic link di Supabase.
  • Analytics — app/layout.tsx carica uno script di analytics in versione cookieless. Anche se non piazza cookie, dichiaralo lo stesso.
  • Crisp — presente in config.ts, ma con id: "" è spento. Citalo solo se lo accendi.

Se hai tolto o sostituito uno di questi, togli o sostituisci anche la riga corrispondente nel prompt. Una policy che nomina un fornitore che non usi è peggio di una policy generica.

4. Scrivi il prompt e mandalo

Metti insieme i due pezzi e incollalo nel modello che preferisci — ChatGPT, Claude, quello che hai aperto. Una versione con i fornitori dentro:

prompt
bash
Sei un avvocato. Scrivimi una privacy policy semplice e leggibile per il mio sito.
Contesto:
- Sito: https://iltuodominio.com
- Nome: IlTuoProdotto
- Descrizione: [una riga su cosa fa]
- Dati personali raccolti: nome, email, dati di fatturazione
- Dati non personali: cookie tecnici e analytics
- Finalità: creazione account, gestione ordini, supporto
- Fornitori terzi che trattano i dati:
  - Supabase (autenticazione e database)
  - Stripe (pagamenti; i dati della carta non transitano dai nostri server)
  - Resend (email transazionali)
  - [analytics, se lo usi]
- Condivisione dati: nessuna vendita o cessione a terzi oltre ai fornitori sopra
- Minori: non raccogliamo dati da minori di 13 anni
- Diritti dell'utente: accesso, rettifica e cancellazione scrivendo al contatto
- Aggiornamenti: comunicati via email
- Contatto: ciao@iltuodominio.com

Scrivila in italiano, con la data di oggi. Non aggiungere spiegazioni o commenti tuoi.

Se i tuoi utenti stanno in Europa, aggiungi una riga per chiedere i punti su base giuridica del trattamento, tempi di conservazione e diritti GDPR. Sono i tre paragrafi che un modello salta se non glieli chiedi.

5. Incolla il risultato nella pagina

Il testo va dentro il <pre> di app/privacy-policy/page.tsx, che è già impostato con whitespace-pre-wrap: i tuoi a capo restano, il testo si adatta alla larghezza.

app/privacy-policy/page.tsx
tsx
<pre
  className="leading-relaxed whitespace-pre-wrap font-sans text-base"
  style={{ color: "var(--ink)" }}
>
  {`Ultimo aggiornamento: 24 agosto 2026

Grazie per aver visitato IlTuoProdotto...`}
</pre>

Due trappole. La prima: il testo sta dentro un template literal, quindi se la risposta contiene un backtick o la sequenza dollaro-graffa il build si rompe — toglili o mettici davanti un backslash. La seconda: aggiorna la riga della data, perché è la prima cosa che guarda chi legge una policy.

Poi ripeti lo stesso giro con app/tos/page.tsx: ha il suo prompt in cima, con in più le voci su proprietà del codice, rimborsi e legge applicabile. Il Footer già linka entrambe le pagine nella colonna LEGALE, quindi non devi toccare altro.

6. Generarla via codice, se proprio vuoi

Nel repo c'è libs/gpt.ts, un helper minimale che chiama l'API di OpenAI con axios. Non c'è nessun SDK: fa un POST diretto a https://api.openai.com/v1/chat/completions, logga il prompt e la risposta in console insieme ai token consumati, e restituisce il contenuto del primo messaggio. Se la chiamata fallisce non lancia: logga l'errore e ritorna null.

libs/gpt.ts
ts
export const sendOpenAi = async (
  messages: any[],
  userId: number,
  max = 100,
  temp = 1
) => {
  const url = 'https://api.openai.com/v1/chat/completions';

  const body = JSON.stringify({
    model: 'gpt-4',
    messages,
    max_tokens: max,
    temperature: temp,
    user: userId,
  });

  const options = {
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
  };

  try {
    const res = await axios.post(url, body, options);
    return res.data.choices[0].message.content;
  } catch (e) {
    console.error('GPT Error: ' + e?.response?.status, e?.response?.data);
    return null;
  }
};
app/api/genera-policy/route.ts
ts
import { sendOpenAi } from "@/libs/gpt";

const testo = await sendOpenAi(
  [{ role: "user", content: prompt }],
  1,      // userId — finisce nel campo "user" della richiesta a OpenAI
  3000,   // max_tokens: il default è 100, troppo poco per una policy
  0.4     // temperature bassa: vuoi testo prevedibile, non creativo
);

if (!testo) {
  // sendOpenAi ritorna null in caso di errore, non lancia
}

Tre avvertenze pratiche. Il max di default è 100 token: se non lo alzi ti torna una policy troncata a metà frase. La chiave OPENAI_API_KEY non è in .env.example, devi aggiungerla tu (vedi Configurare le variabili). E sendOpenAi va chiamata solo lato server — route handler o server component — altrimenti la chiave finisce nel bundle del browser.

Detto questo: per due pagine legali che scrivi una volta sola, il copia-incolla del passo 5 è più veloce e non ti costa token. libs/gpt.ts è lì per quando l'AI serve dentro il prodotto, non per generarti il footer.

7. Fallo leggere a un umano

Questa parte non è opzionale. Un testo legale generato da un modello è una bozza: può citare leggi sbagliate, dimenticare obblighi che valgono nel tuo paese o affermare cose che non corrispondono a come tratti davvero i dati. Prima di pubblicare rileggi riga per riga e verifica che ogni frase descriva quello che il tuo codice fa realmente, poi fallo controllare a un avvocato — a maggior ragione se vendi in Europa o tratti dati sensibili.

Questa guida spiega un flusso di lavoro tecnico, non è consulenza legale. La responsabilità di quello che pubblichi sul tuo dominio resta tua.