Skip to content
Merged
162 changes: 72 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,103 @@

# World Video Guide

A personal web app that lets you **explore the world through geolocated YouTube videos by country**.
Users can suggest content, while **moderators/admins** review it (approve/reject) before it appears publicly on the map.

## Features
**World Video Guide** è un'applicazione web interattiva progettata per esplorare e scoprire contenuti video da tutto il mondo. Attraverso una mappa globale, gli utenti possono navigare tra le nazioni, visualizzare video suggeriti dalla community e partecipare a discussioni. Il progetto è stato sviluppato con un'architettura moderna e tecnologie all'avanguardia, ponendo un forte accento sull'esperienza utente, la reattività e la scalabilità.

- **Interactive map** (world-atlas) with countries highlighted based on the number of approved videos.
- **Country overlay** with an approved video feed and **category** filters.
- **Video suggestion** (YouTube URL) with multiple categories + custom category.
- **Firebase Authentication**: Google, GitHub, Apple, Email/Password.
- **Moderation**: pending queue, approve/reject with reason.
- **User profile**: stats, suggested videos list, approved/rejected history, video removal.
- **Roles**: `user`, `moderator`, `admin` (admin only: user/role management).
L'applicazione è concepita come una **Progressive Web App (PWA)**, garantendo un'esperienza nativa su dispositivi mobili, funzionalità offline e notifiche push per un coinvolgimento continuo.

## Stack
## Sito Online

- **Vite** + **React 19** + **TypeScript**
- **Tailwind CSS v4**
- **Firebase** (Auth + Firestore) + **Firebase Hosting**
- **react-router-dom** (routing)
- **react19-simple-maps** (map)
- **Framer Motion** (UI animations) + Swiper (region selector on mobile)
Il sito è attualmente online grazie a **Firebase Hosting** e puoi provarlo al seguente link: [https://prova-mappa-f1d90.web.app/](https://prova-mappa-f1d90.web.app/).

## Running Locally
## Architettura e Stack Tecnologico

### Requirements
Il progetto si basa su un'architettura client-server disaccoppiata, con un frontend reattivo e un backend serverless robusto.

- Node.js (recommended **>= 20 LTS**)
- A Firebase project with Auth + Firestore enabled
### Frontend

### Setup
* **React 19**: Utilizzato per costruire un'interfaccia utente dinamica e component-based. L'uso di React Hooks (come `useState`, `useEffect`, `useRef`) permette una gestione dello stato e del ciclo di vita dei componenti pulita ed efficiente.
* **TypeScript**: Garantisce un codice robusto, manutenibile e con type-safety, riducendo i bug in fase di sviluppo.
* **Vite**: Un build tool di nuova generazione che offre un'esperienza di sviluppo estremamente rapida grazie al suo server di sviluppo nativo ESM e al bundling ottimizzato per la produzione.
* **Tailwind CSS**: Un framework CSS utility-first che permette di creare design complessi e reattivi direttamente nell'HTML, garantendo coerenza stilistica e rapidità di sviluppo.
* **Framer Motion**: Una libreria di animazione per React che consente di creare animazioni fluide e complesse con un'API dichiarativa e intuitiva.
* **React Router**: Per la gestione del routing lato client, permettendo una navigazione fluida tra le diverse sezioni dell'applicazione (Home, Profilo, Admin).
* **Vite PWA Plugin**: Per trasformare l'applicazione in una Progressive Web App, gestendo il service worker, la cache e il manifest dell'app.

1) Install dependencies:
### Backend & Servizi

```bash
npm ci
```
* **Firebase**: Una piattaforma completa di Google che fornisce i seguenti servizi:
* **Firestore**: Un database NoSQL flessibile e scalabile utilizzato per memorizzare tutti i dati dell'applicazione, come utenti, video, commenti, categorie e segnalazioni. Le query in tempo reale garantiscono che l'interfaccia utente sia sempre sincronizzata con il database.
* **Firebase Authentication**: Gestisce l'autenticazione degli utenti tramite provider multipli (Google, GitHub, Email/Password) in modo sicuro e scalabile.
* **Firebase Cloud Messaging**: Utilizzato per inviare notifiche push multi-dispositivo, permettendo agli utenti di rimanere aggiornati sui nuovi video nei paesi che seguono.
* **Render (Backend Service)**: Un servizio di hosting per un piccolo backend Node.js/Express che gestisce la logica di iscrizione e invio delle notifiche push, interfacciandosi con Firebase Cloud Messaging.

2) Configure environment variables:
## Feature Principali

- Create a `.env.local` file starting from `.env.example`.
- Fill in the values using Firebase Console -> Project settings -> Your apps -> (Web app config).
L'applicazione offre un'ampia gamma di funzionalità pensate per la community e gli amministratori.

```bash
cp .env.example .env.local
```

3) Start in development mode:

```bash
npm run dev
```
### Per gli Utenti

## Available Scripts
* **Esplorazione Globale**: Una mappa del mondo interattiva (`@vnedyalk0v/react19-simple-maps`) permette di selezionare i paesi e visualizzare i video associati.
* **Autenticazione Multi-Provider**: Login semplice e sicuro tramite Google, GitHub o credenziali email/password.
* **Suggerimento di Video**: Gli utenti autenticati possono suggerire nuovi video per ogni paese, che verranno poi sottoposti a revisione.
* **Chat per Paese**: Ogni nazione ha una chat dedicata dove gli utenti possono discutere e scambiarsi opinioni.
* **Notifiche Push**: Gli utenti possono iscriversi alle notifiche per i loro paesi preferiti e ricevere un avviso quando un nuovo video viene approvato. Il sistema supporta notifiche su più dispositivi per lo stesso utente.
* **Profilo Utente**: Una pagina personale dove gli utenti possono visualizzare le statistiche sui video suggeriti (in attesa, approvati, rifiutati) e gestire i paesi seguiti.
* **Segnalazione di Contenuti**: Possibilità di segnalare commenti e video inappropriati per la revisione da parte dei moderatori.
* **PWA (Progressive Web App)**: L'applicazione è installabile sulla home screen dei dispositivi mobili e offre un' ottima esperienza offline.

- `npm run dev` — starts the development server
- `npm run build` — production build (TypeScript + Vite)
- `npm run preview` — build preview
- `npm run lint` — ESLint
### Per gli Amministratori

## Firebase: Recommended Configuration
* **Pannello di Amministrazione**: Un'area riservata per moderatori e amministratori con strumenti per la gestione dei contenuti.
* **Revisione dei Video**: Un'interfaccia per approvare o rifiutare i video suggeriti dagli utenti. In caso di rifiuto, è possibile specificare una motivazione.
* **Gestione Dinamica delle Categorie**:
* Gli amministratori possono modificare le categorie associate a un video prima di approvarlo.
* Se una categoria non esiste, viene creata dinamicamente al momento dell'approvazione del video.
* **Gestione degli Alias**: È possibile unire categorie "grezze" (es. "cucina tipica") a categorie ufficiali (es. "cibo") creando un alias. I futuri suggerimenti con lo stesso nome verranno automaticamente associati alla categoria corretta.
* **Gestione delle Segnalazioni**: Liste separate per visualizzare e gestire i commenti e i video segnalati dagli utenti.
* **Gestione Utenti**: Visualizzazione di tutti gli utenti registrati con la possibilità di modificarne il ruolo (user, moderator, admin).

### Authentication
## Istruzioni per l'Installazione Locale

Enable the providers you want to use:
Per eseguire il progetto in locale, segui questi passaggi:

- Google
- GitHub
- Apple (`apple.com` via OAuth provider)
- Email/Password
1. **Clona il repository:**
```bash
git clone https://github.com/DaPrato4/world-video-guide
cd world-video-guide
```

Note: if you enable GitHub/Apple, you must also configure the **OAuth redirect URIs** in the provider settings.
2. **Installa le dipendenze:**
Assicurati di avere Node.js (versione 18 o successiva) e npm installati.
```bash
npm install
```

### Firestore: Used Collections
3. **Configura le variabili d'ambiente:**
Crea un file `.env` nella root del progetto e copia il contenuto del template qui sotto.

The app relies on three collections:
4. **Avvia il server di sviluppo:**
```bash
npm run dev
```
L'applicazione sarà disponibile all'indirizzo `http://localhost:5173`.

- `users` (doc id = `uid`)
- `uid`, `email`, `displayName`, `role`, `photoURL`
- `stats`: `pendingVideos`, `approvedVideos`, `rejectedVideos`, `suggestedVideos`
- `videos`
- `url` (YouTube)
- `countryCode` (numeric, e.g. `380`)
- `status`: `pending | approved | rejected`
- `categories`: `string[]`
- `submittedBy`: `uid`
- `createdAt` (Date/Timestamp)
- `rejectionReason` (only if rejected)
- `categories`
- documents with a `category` field (label shown in the UI)
### Template per il file `.env`

### Indexes
Crea un file chiamato `.env` nella directory principale del progetto e inserisci le tue credenziali Firebase.

In the user profile, a query `where(submittedBy == uid) + orderBy(createdAt desc)` is executed.
If Firestore requires it, create the **composite index** suggested by the error link in the console.
```env
# Firebase Configuration
VITE_FIREBASE_API_KEY="YOUR_API_KEY"
VITE_FIREBASE_AUTH_DOMAIN="YOUR_AUTH_DOMAIN"
VITE_FIREBASE_PROJECT_ID="YOUR_PROJECT_ID"
VITE_FIREBASE_STORAGE_BUCKET="YOUR_STORAGE_BUCKET"
VITE_FIREBASE_MESSAGING_SENDER_ID="YOUR_MESSAGING_SENDER_ID"
VITE_FIREBASE_APP_ID="YOUR_APP_ID"
VITE_FIREBASE_VAPID_KEY="YOUR_VAPID_KEY_FOR_FCM"

## Deploy (Firebase Hosting)

The project is already configured as an SPA (rewrite to `index.html`).

```bash
npm run build
firebase deploy --only hosting
```

If you want to target your own Firebase project:

- edit `.firebaserc` or use `firebase use --add`

## Technical Notes

- Video metadata (title/thumbnail) is fetched through **YouTube oEmbed**.
- Country info (name/flag/capital coordinates) comes from **REST Countries**.
- The map uses the **world-atlas** dataset (TopoJSON) fetched from a CDN.

## License

MIT - see [LICENSE](LICENSE).
---

Questo progetto rappresenta una dimostrazione completa di come integrare diverse tecnologie moderne per creare un'applicazione web ricca di funzionalità, scalabile e manutenibile.
Binary file removed public/screenshot-1.png
Binary file not shown.
Binary file removed public/screenshot-2.png
Binary file not shown.
7 changes: 4 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ export default function App() {
);

if (addedCountries.length > 0 || removedCountries.length > 0) {
console.log("Rilevate nuove iscrizioni da un altro dispositivo:", addedCountries);
console.log("Rilevate cancellazioni da un altro dispositivo:", removedCountries);

try {
const currentToken = await requestForToken();
Expand Down Expand Up @@ -122,7 +120,6 @@ export default function App() {
})
});
}
console.log("Dispositivo auto-sincronizzato con i nuovi paesi!");
}
} catch (error) {
console.error("Errore di auto-sincronizzazione in background:", error);
Expand All @@ -139,6 +136,8 @@ export default function App() {
photoURL: firebaseUser.photoURL ?? "",
stats: userData.stats || { pendingVideos: 0, approvedVideos: 0, rejectedVideos: 0, suggestedVideos: 0 },
subscriptions: userData.subscriptions || [],
reportedComments: userData.reportedComments || [],
reportedVideos: userData.reportedVideos || [],
});
} else {
// Se il documento non esiste, lo creiamo (fatto una sola volta)
Expand All @@ -150,6 +149,8 @@ export default function App() {
photoURL: firebaseUser.photoURL ?? "",
stats: { pendingVideos: 0, approvedVideos: 0, rejectedVideos: 0, suggestedVideos: 0 },
subscriptions: [],
reportedComments: [],
reportedVideos: [],
};
setDoc(userDocRef, newUser);
setUser(newUser);
Expand Down
5 changes: 4 additions & 1 deletion src/components/admin/CategoryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export default function CategoryEditor({
const nextCategory = customCategory.trim().toLowerCase();
if (!nextCategory) return;

setEditedCategories((current) => (current.includes(nextCategory) ? current : [...current, nextCategory]));
setEditedCategories((current) =>
current.some((item) => item.toLocaleLowerCase() === nextCategory)
? current
: [...current, nextCategory]);
setCustomCategory("");
};

Expand Down
Loading
Loading