Commit a87618c6 by PLN (Algolia)

chore: Remove Paradiso

parent 84be633f
import React, { useState } from 'react';
import Image from 'next/image';
import styles from '@/styles/paradiso.module.css';
const MovieCard = ({ movie, onVote }) => {
const [isHovered, setIsHovered] = useState(false);
const [isVoting, setIsVoting] = useState(false);
const handleVote = async (e) => {
e.preventDefault();
e.stopPropagation();
if (isVoting) return;
setIsVoting(true);
await onVote(movie.objectID);
setIsVoting(false);
};
return (
<div
className={styles.movieCard}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className={styles.posterContainer}>
{movie.poster ? (
<Image
src={movie.poster}
alt={movie.title}
width={200}
height={300}
className={styles.poster}
unoptimized
/>
) : (
<div className={styles.noPoster}>
<span>{movie.title}</span>
</div>
)}
{isHovered && (
<div className={styles.movieOverlay}>
<h3 className={styles.movieTitle}>{movie.title}</h3>
<div className={styles.movieYear}>{movie.year}</div>
<div className={styles.movieRating}>
{movie.imdbRating || 'N/A'}
</div>
<div className={styles.movieGenres}>
{movie.genre && movie.genre.slice(0, 3).join(' • ')}
</div>
<p className={styles.moviePlot}>
{movie.plot && movie.plot.length > 150
? `${movie.plot.substring(0, 150)}...`
: movie.plot}
</p>
<div className={styles.movieActions}>
<button
className={styles.voteButton}
onClick={handleVote}
disabled={isVoting}
>
{isVoting ? 'Voting...' : `Vote (${movie.votes || 0})`}
</button>
</div>
</div>
)}
</div>
<div className={styles.movieInfo}>
<h4>{movie.title}</h4>
<div className={styles.movieMeta}>
<span>{movie.year}</span>
<span className={styles.votes}>{movie.votes || 0} votes</span>
</div>
</div>
</div>
);
};
export default MovieCard;
\ No newline at end of file
import React, { useState } from 'react';
import styles from '../styles/paradiso.module.css';
const MovieSearch = ({ onAddMovie }) => {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [searching, setSearching] = useState(false);
const [error, setError] = useState(null);
// Function to search for movies via TMDB
const searchMovies = async (e) => {
e.preventDefault();
if (!searchQuery.trim()) return;
setSearching(true);
setError(null);
try {
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=3e1dd2bcd5e1265d986c9a1501d6f8c0&query=${encodeURIComponent(searchQuery)}&language=en-US&page=1&include_adult=false`
);
if (!response.ok) {
throw new Error('Failed to search for movies');
}
const data = await response.json();
setSearchResults(data.results || []);
} catch (error) {
console.error('Error searching for movies:', error);
setError('Failed to search for movies. Please try again later.');
setSearchResults([]);
} finally {
setSearching(false);
}
};
// Function to add a movie from search results
const handleAddMovie = (movie) => {
const newMovieObj = {
id: movie.id.toString(),
title: movie.title,
votes: 0,
addedDate: new Date().toISOString(),
poster: movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : null,
description: movie.overview || null,
year: movie.release_date ? movie.release_date.substring(0, 4) : null
};
onAddMovie(newMovieObj);
setSearchResults([]);
setSearchQuery('');
};
return (
<div className={styles.searchContainer}>
<h3>Find a Movie</h3>
{/* Search Form */}
<form onSubmit={searchMovies} className={styles.searchBar}>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search for a movie..."
/>
<button
type="submit"
disabled={searching || !searchQuery.trim()}
>
{searching ? 'Searching...' : 'Search'}
</button>
</form>
{/* Error Message */}
{error && (
<div className={styles.errorMessage}>
{error}
</div>
)}
{/* Search Results */}
{searchResults.length > 0 && (
<div className={styles.searchResults}>
<h4>Search Results</h4>
<div className={styles.movieGrid}>
{searchResults.slice(0, 5).map(movie => (
<div key={movie.id} className={styles.searchResultItem}>
{movie.poster_path ? (
<img
src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
alt={movie.title}
className={styles.searchResultPoster}
/>
) : (
<div className={styles.searchResultPoster} style={{
backgroundColor: '#eee',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999'
}}>
No Image
</div>
)}
<div className={styles.searchResultInfo}>
<h5>{movie.title} {movie.release_date && `(${movie.release_date.substring(0, 4)})`}</h5>
{movie.overview && (
<p>{movie.overview.length > 100 ? `${movie.overview.substring(0, 100)}...` : movie.overview}</p>
)}
<button
onClick={() => handleAddMovie(movie)}
className={styles.addButton}
>
Add to List
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* No Results Message */}
{searchResults.length === 0 && searchQuery && !searching && !error && (
<div className={styles.noResults}>
No results found for "{searchQuery}". Try a different search term.
</div>
)}
</div>
);
};
export default MovieSearch;
\ No newline at end of file
# Paradiso Bot Deployment Guide
This guide will help you deploy the Paradiso movie voting bot on your Debian server or using Replit, as you prefer.
## Environment Variables
The bot requires the following environment variables:
- `DISCORD_TOKEN`: Your Discord bot token
- `ALGOLIA_APP_ID`: Your Algolia application ID
- `ALGOLIA_API_KEY`: Your Algolia API key
- `ALGOLIA_MOVIES_INDEX`: The Algolia index for movies (e.g., `paradiso_movies`)
- `ALGOLIA_VOTES_INDEX`: The Algolia index for votes (e.g., `paradiso_votes`)
- `MOVIE_DATA_SOURCE`: Preferred movie data source (`tmdb`, `omdb`, or `fallback`, defaults to `tmdb`)
At least one of the following API keys is required:
- `TMDB_API_KEY`: Your TMDB API key
- `OMDB_API_KEY`: Your OMDB API key
## Option 1: Deploy on Your Debian Server
### Prerequisites
- Debian 9+ (tested on Debian 9.13 Stretch)
- Python 3.7+ (Python 3.5+ should work, but 3.7+ is recommended)
- pip (Python package manager)
- systemd for service management
### Installation Steps
1. Log in to your server and create a directory for the bot:
```bash
mkdir -p /opt/paradiso-bot
cd /opt/paradiso-bot
```
2. Download the bot files or clone the repository:
```bash
# If you have git installed
git clone https://your-repo-url.git .
# Or manually download and upload the files
```
3. Set up a Python virtual environment (recommended):
```bash
# Install venv if not already installed
apt-get update
apt-get install -y python3-venv
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
```
4. Install the required dependencies:
```bash
pip install discord.py python-dotenv algoliasearch requests
```
5. Create a `.env` file:
```bash
nano .env
```
6. Add your environment variables to the `.env` file:
```
DISCORD_TOKEN=your_discord_token
ALGOLIA_APP_ID=your_algolia_app_id
ALGOLIA_API_KEY=your_algolia_api_key
ALGOLIA_MOVIES_INDEX=paradiso_movies
ALGOLIA_VOTES_INDEX=paradiso_votes
MOVIE_DATA_SOURCE=tmdb
TMDB_API_KEY=your_tmdb_api_key
OMDB_API_KEY=your_omdb_api_key
```
7. Create a systemd service file:
```bash
sudo nano /etc/systemd/system/paradiso-bot.service
```
8. Add the following content to the service file:
```
[Unit]
Description=Paradiso Discord Bot
After=network.target
[Service]
User=your_username
WorkingDirectory=/opt/paradiso-bot
ExecStart=/opt/paradiso-bot/venv/bin/python bot.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
```
9. Enable and start the service:
```bash
sudo systemctl daemon-reload
sudo systemctl enable paradiso-bot.service
sudo systemctl start paradiso-bot.service
```
10. Check the status of the service:
```bash
sudo systemctl status paradiso-bot.service
```
### Changing Your Server Hostname (from erable.plnech.fr to nech.pl)
To change your server hostname on Debian:
1. Edit the hostname file:
```bash
sudo nano /etc/hostname
```
2. Replace the current hostname with the new one:
```
nech.pl
```
3. Edit the hosts file:
```bash
sudo nano /etc/hosts
```
4. Update the relevant line:
```
127.0.1.1 nech.pl
```
5. Apply the changes:
```bash
sudo hostname nech.pl
```
6. Restart networking and related services:
```bash
sudo systemctl restart networking
```
7. If you have configured DNS records, update them by:
- Logging into your domain registrar or DNS provider
- Updating the A/AAAA record for `nech.pl` to point to your server's IP
- If using SSL certificates, you may need to renew them for the new domain
8. Reboot your server to ensure all services are using the new hostname:
```bash
sudo reboot
```
## Option 2: Deploy on Replit
### Prerequisites
- A Replit account
- A UptimeRobot account (to keep the bot awake)
### Installation Steps
1. Go to [Replit](https://replit.com) and sign up or log in
2. Click the "+ Create" button
3. Select "Python" as the template
4. Name your repl "ParadisoBot" or similar
5. Click "Create Repl"
6. Upload the `bot.py` file to your Repl
7. Create a `keep_alive.py` file with the following content:
```python
from flask import Flask
from threading import Thread
app = Flask('')
@app.route('/')
def home():
return "Paradiso Bot is alive!"
def run():
app.run(host='0.0.0.0', port=8080)
def keep_alive():
t = Thread(target=run)
t.start()
```
8. Modify the end of your `bot.py` file to use the keep_alive function:
```python
# At the top of the file, add:
from keep_alive import keep_alive
# At the bottom of your file, replace:
if __name__ == "__main__":
client.run(DISCORD_TOKEN)
# With:
if __name__ == "__main__":
keep_alive() # Keep the bot alive
client.run(DISCORD_TOKEN)
```
9. Add the environment variables in Replit:
- Click on the 🔒 icon in the sidebar (or find "Secrets" in the "Tools" menu)
- Add each of the environment variables listed above
10. Create a `pyproject.toml` file for dependencies:
```toml
[tool.poetry]
name = "paradiso-bot"
version = "0.1.0"
description = "Discord bot for Paradiso movie night voting"
authors = ["Your Name <your.email@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
discord = "^2.0.0"
python-dotenv = "^0.21.0"
algoliasearch = "^2.6.2"
requests = "^2.28.1"
Flask = "^2.2.2"
wikipedia = "^1.4.0" # For fallback movie data
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
11. Click the "Run" button to start the bot
12. To keep the bot running 24/7, set up UptimeRobot:
- Go to [UptimeRobot](https://uptimerobot.com/)
- Create a free account
- Click "Add New Monitor"
- Select "HTTP(s)" as the monitor type
- Enter a friendly name like "Paradiso Bot"
- Enter the URL of your Repl webview (e.g., `https://paradisobot.yourusername.repl.co`)
- Set the monitoring interval to 5 minutes
- Click "Create Monitor"
## Movie Data Source Configuration
The bot supports multiple movie data sources in a fallback cascade:
1. Configure which API to use as primary with the `MOVIE_DATA_SOURCE` environment variable:
- `tmdb`: Use The Movie Database API (recommended)
- `omdb`: Use Open Movie Database API
- `fallback`: Attempt to use all available sources in priority order
2. The system will work with any of these configurations:
- TMDB API only: Set `TMDB_API_KEY` and `MOVIE_DATA_SOURCE=tmdb`
- OMDB API only: Set `OMDB_API_KEY` and `MOVIE_DATA_SOURCE=omdb`
- Both APIs: Set both keys and choose your preferred order with `MOVIE_DATA_SOURCE`
3. If both APIs fail, the bot will make a best effort to find information using public sources.
## Verifying the Bot is Working
1. Invite the bot to your Discord server using the OAuth2 URL from the Discord Developer Portal
2. Try using the `/help` command in your server to see all available commands
3. Test adding a movie with `/add [movie title]`
## Monitoring and Troubleshooting
### Checking Logs
On your Debian server, you can check the bot logs using:
```bash
sudo journalctl -u paradiso-bot.service
```
For recent logs only:
```bash
sudo journalctl -u paradiso-bot.service -n 50 --no-pager
```
To follow logs in real-time:
```bash
sudo journalctl -u paradiso-bot.service -f
```
### Common Issues
- **Bot not responding to commands**:
- Check if the bot is running: `systemctl status paradiso-bot`
- Verify Discord token is correct
- Ensure the bot has the proper permissions in Discord
- **Movie search not working**:
- Check if TMDB/OMDB API keys are correct
- Look for API rate limiting errors in logs
- Try switching between data sources
- **Algolia operations failing**:
- Verify Algolia credentials and index names
- Check if indices have been properly created
- Ensure you have the right permissions set for your API key
## Updating the Bot
When you want to update the bot:
1. Stop the current running instance:
```bash
sudo systemctl stop paradiso-bot.service
```
2. Update the code files:
```bash
cd /opt/paradiso-bot
# Pull updates from git or upload new files
```
3. Restart the service:
```bash
sudo systemctl start paradiso-bot.service
```
## Additional Resources
- [Discord.py Documentation](https://discordpy.readthedocs.io/)
- [Algolia Documentation](https://www.algolia.com/doc/)
- [TMDB API Documentation](https://developers.themoviedb.org/3)
- [OMDB API Documentation](http://www.omdbapi.com/)
- [Debian Service Management](https://wiki.debian.org/systemd)
- [Replit Documentation](https://docs.replit.com/)
- [UptimeRobot Documentation](https://uptimerobot.com/help/)
\ No newline at end of file
# Paradiso - Movie Night Voting System
Paradiso is a complete movie night voting system consisting of:
1. A Next.js web interface integrated with your personal site
2. A Discord bot for interaction through Discord
3. Algolia as a database and search engine
4. OMDb API for movie metadata
This system allows you and your friends to vote on movies for your next movie night, ensuring a fair and fun selection process.
## System Architecture
- **Data Storage**: [Algolia](https://www.algolia.com/) (Search & Database)
- **Movie Data**: [OMDb API](https://www.omdbapi.com/) (Open Movie Database)
- **Frontend**: Next.js component on your personal site
- **Bot**: Python Discord bot using discord.py
## Setup Instructions
### Step 1: Set Up Algolia
1. Create a free [Algolia](https://www.algolia.com/) account
2. Create a new application (or use an existing one)
3. Get your Application ID and Admin API Key from the dashboard
4. Run the setup script to configure indices and generate API keys:
```bash
# Install the required package
pip install algoliasearch
# Run the setup script
python setup.py --admin-key YOUR_ADMIN_API_KEY --app-id YOUR_APP_ID
```
The setup script creates:
- A `paradiso_movies` index for storing movie data
- A `paradiso_votes` index for tracking votes
- Necessary API keys with appropriate permissions
- Configuration files for the web app and Discord bot
### Step 2: Get an OMDb API Key
1. Go to [OMDb API](https://www.omdbapi.com/apikey.aspx)
2. Sign up for a free API key (1,000 daily requests)
3. Check your email and activate your key
4. Add your key to the `.env.frontend` and `.env.bot` files generated by the setup script
### Step 3: Set Up the Next.js Frontend
1. Create the API endpoints in your Next.js project:
- `pages/api/paradiso/vote.js` - For voting on movies
- `pages/api/paradiso/search-movie.js` - For searching movies via OMDb API
- `pages/api/paradiso/add-movie.js` - For adding movies to Algolia
2. Create the Paradiso page:
- `pages/paradiso/index.js` - The main movie voting interface
3. Install the required packages:
```bash
npm install algoliasearch react-instantsearch-dom
```
4. Add the environment variables from `.env.frontend` to your Next.js project:
```bash
# .env.local in your Next.js project
NEXT_PUBLIC_ALGOLIA_APP_ID=YOUR_APP_ID
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY=YOUR_SEARCH_KEY
NEXT_PUBLIC_ALGOLIA_INDEX=paradiso_movies
ALGOLIA_ADMIN_API_KEY=YOUR_ADMIN_API_KEY
OMDB_API_KEY=YOUR_OMDB_API_KEY
```
### Step 4: Set Up the Discord Bot
1. Create a new Discord application and bot at the [Discord Developer Portal](https://discord.com/developers/applications)
2. Get your bot token
3. Add the bot to your Discord server with appropriate permissions:
- Read Messages/View Channels
- Send Messages
- Use Slash Commands
- Embed Links
4. Install the required Python packages:
```bash
pip install discord.py python-dotenv algoliasearch requests
```
5. Create a `.env` file for the bot with the environment variables from `.env.bot`:
```bash
# .env for the Discord bot
DISCORD_TOKEN=YOUR_DISCORD_BOT_TOKEN
ALGOLIA_APP_ID=YOUR_APP_ID
ALGOLIA_API_KEY=YOUR_SECURED_API_KEY
ALGOLIA_MOVIES_INDEX=paradiso_movies
ALGOLIA_VOTES_INDEX=paradiso_votes
OMDB_API_KEY=YOUR_OMDB_API_KEY
```
6. Run the bot:
```bash
python bot.py
```
## Usage
### Web Interface
The web interface will be available at `https://your-site.com/paradiso`. Here, users can:
- Search for movies
- Add new movies
- Vote for movies
- See the top voted movies
### Discord Bot
The Discord bot provides the following slash commands:
- `/movies` - List all movies in the voting queue
- `/add [title]` - Add a movie to the voting queue
- `/vote [title]` - Vote for a movie in the queue
- `/remove [title]` - Remove a movie from the voting queue (admin only)
- `/top [count]` - Show the top voted movies (default: top 5)
- `/random` - Suggest a random movie from the list
- `/help` - Show help for all commands
## Security Considerations
The system is secured in the following ways:
1. **Frontend**: Uses a secured API key with restricted permissions
2. **Bot**: Uses a different secured API key with its own permissions
3. **Rate Limiting**: API calls are rate-limited to prevent abuse
4. **User Tokens**: Uses tokens to identify users and prevent duplicate votes
## Limitations and Notes
- The free tier of Algolia provides 10,000 records and 10,000 operations per month, more than enough for a personal movie voting system
- The free tier of OMDb API allows 1,000 requests per day
- The secured API keys generated by the setup script are valid for 1 year, after which you'll need to generate new ones
## Customization
You can customize various aspects of the system:
- Change the index prefix in the setup script
- Modify the web interface styling to match your site
- Adjust the Discord bot's embed colors and messages
- Add additional commands to the bot
## Troubleshooting
- If votes aren't being recorded, check your Algolia API keys and permissions
- If movie searches fail, verify your OMDb API key is active
- If the Discord bot isn't responding, ensure it has the correct permissions in your server
## Future Improvements
- Add user authentication for the web interface
- Implement more advanced voting mechanics (e.g., ranked voting)
- Add movie night scheduling features
- Create a shared watchlist for watched movies
## Credits
- Movie data provided by [OMDb API](https://www.omdbapi.com/)
- Search and database powered by [Algolia](https://www.algolia.com/)
- Discord integration using [discord.py](https://discordpy.readthedocs.io/)
\ No newline at end of file
# Hosting the Paradiso Discord Bot for Free
This guide explains how to host your Paradiso Discord bot for free using Replit, ensuring it runs 24/7 without any costs.
## What is Replit?
[Replit](https://replit.com) is a browser-based IDE that allows you to write, run, and host code in the cloud. It's perfect for hosting Discord bots because:
1. It offers a free tier with no credit card required
2. It can keep your bot running 24/7 (with some setup)
3. It's easy to use and doesn't require server management
## Step 1: Create a Replit Account
1. Go to [Replit](https://replit.com)
2. Sign up for a free account
3. Verify your email address
## Step 2: Create a New Repl
1. Click the "+ Create" button
2. Select "Python" as the template
3. Name your repl "ParadisoBot" or something similar
4. Click "Create Repl"
## Step 3: Set Up Your Bot Files
1. Upload the `paradiso_bot.py` file to your Repl
2. Create a new file called `keep_alive.py` with the following code:
```python
from flask import Flask
from threading import Thread
app = Flask('')
@app.route('/')
def home():
return "Paradiso Bot is alive!"
def run():
app.run(host='0.0.0.0', port=8080)
def keep_alive():
t = Thread(target=run)
t.start()
```
3. Modify the end of your `paradiso_bot.py` file to use the keep_alive function:
```python
# At the top of the file, add:
from keep_alive import keep_alive
# At the bottom of your file, replace:
if __name__ == "__main__":
client.run(DISCORD_TOKEN)
# With:
if __name__ == "__main__":
keep_alive() # Keep the bot alive
client.run(DISCORD_TOKEN)
```
## Step 4: Set Up Environment Variables
Replit provides a secure way to store sensitive information like API keys.
1. In your Repl, click on the 🔒 icon in the sidebar (or find "Secrets" in the "Tools" menu)
2. Add the following secrets:
- Key: `DISCORD_TOKEN`, Value: `your-discord-bot-token`
- Key: `ALGOLIA_APP_ID`, Value: `your-algolia-app-id`
- Key: `ALGOLIA_API_KEY`, Value: `your-algolia-api-key`
- Key: `ALGOLIA_MOVIES_INDEX`, Value: `paradiso_movies`
- Key: `ALGOLIA_VOTES_INDEX`, Value: `paradiso_votes`
- Key: `OMDB_API_KEY`, Value: `your-omdb-api-key`
## Step 5: Install Dependencies
1. Create a new file called `pyproject.toml` with the following content:
```toml
[tool.poetry]
name = "paradiso-bot"
version = "0.1.0"
description = "Discord bot for Paradiso movie night voting"
authors = ["Your Name <your.email@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
discord = "^2.0.0"
python-dotenv = "^0.21.0"
algoliasearch = "^2.6.2"
requests = "^2.28.1"
Flask = "^2.2.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
2. Replit will automatically install the dependencies when you run the bot
## Step 6: Run Your Bot
1. Click the "Run" button at the top of the page
2. Your bot should start up and connect to Discord
3. You'll see a webview showing "Paradiso Bot is alive!"
## Step 7: Keep Your Bot Running 24/7
By default, Replit will stop your bot after some time of inactivity. To keep it running:
1. Go to [UptimeRobot](https://uptimerobot.com/)
2. Create a free account
3. Click "Add New Monitor"
4. Select "HTTP(s)" as the monitor type
5. Enter a friendly name like "Paradiso Bot"
6. Enter the URL of your Repl webview (e.g., `https://paradisobot.yourusername.repl.co`)
7. Set the monitoring interval to 5 minutes
8. Click "Create Monitor"
UptimeRobot will now ping your bot every 5 minutes, keeping it alive 24/7.
## Step 8: Update Your Bot
To update your bot when you make changes:
1. Make changes to the files in your Repl
2. Click the "Stop" button if your bot is running
3. Click the "Run" button to restart your bot with the changes
## Alternative Free Hosting Options
If you prefer not to use Replit, here are some alternatives:
### Railway
[Railway](https://railway.app/) offers a generous free tier:
- 5 projects
- 500 hours of runtime per month
- 1GB memory per container
### Render
[Render](https://render.com/) offers a free tier for web services:
- Free for web services (sleeps after 15 minutes of inactivity)
- Wakes up when receiving a request
### Oracle Cloud Free Tier
[Oracle Cloud](https://www.oracle.com/cloud/free/) offers always-free services:
- 2 AMD-based Compute VMs
- 4 ARM-based Ampere A1 cores and 24 GB memory
- 200 GB of storage
## Troubleshooting
- **Bot crashes or doesn't respond**: Check the console output in Replit for error messages
- **UptimeRobot says the site is down**: Make sure your Flask app is running on port 8080
- **Bot doesn't respond to commands**: Ensure your bot has the correct permissions in your Discord server
- **Algolia operations fail**: Check that your API keys and indices are correctly configured
## Notes
- Replit's free tier may occasionally experience slowdowns during high-traffic periods
- The bot might briefly go offline when Replit performs maintenance updates
- For a more robust solution, consider upgrading to Replit's paid plan or hosting on a VPS
## Additional Resources
- [Replit Documentation](https://docs.replit.com/)
- [Discord.py Documentation](https://discordpy.readthedocs.io/)
- [UptimeRobot Documentation](https://uptimerobot.com/help/)
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import algoliasearch from 'algoliasearch';
import { InstantSearch, SearchBox, Hits, Configure } from 'react-instantsearch-dom';
import Image from 'next/image';
import Layout from '@/components/layout';
import utilStyles from '@/styles/utils.module.css';
import styles from '@/styles/paradiso.module.css';
// Initialize the Algolia client
// TODO CONFIRM .env IS LOADED
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY
);
// Unique user token for this session - in a real app, this would be tied to user authentication
const getUserToken = () => {
if (typeof window === 'undefined') return null;
let userToken = localStorage.getItem('paradiso_user_token');
if (!userToken) {
// Generate a random user token
userToken = 'user_' + Math.random().toString(36).substring(2, 15);
localStorage.setItem('paradiso_user_token', userToken);
}
return userToken;
};
// Movie Hit component - Displays a single movie
const MovieHit = ({ hit, onVote }) => {
return (
<div className={styles.movieCard}>
{hit.poster ? (
<div className={styles.moviePoster}>
<Image
src={hit.poster}
alt={hit.title}
width={200}
height={300}
layout="responsive"
/>
</div>
) : (
<div className={styles.noImagePlaceholder}>No Image</div>
)}
<div className={styles.movieInfo}>
<h3 className={styles.movieTitle}>
{hit.title} {hit.year && <span className={styles.movieYear}>({hit.year})</span>}
</h3>
{hit.director && (
<p className={styles.movieDirector}>Director: {hit.director}</p>
)}
{hit.actors && hit.actors.length > 0 && (
<p className={styles.movieActors}>Starring: {hit.actors.join(', ')}</p>
)}
{hit.plot && (
<p className={styles.moviePlot}>{hit.plot}</p>
)}
<div className={styles.movieMeta}>
{hit.imdbRating && (
<span className={styles.movieRating}> {hit.imdbRating}/10</span>
)}
{hit.source && (
<span className={styles.movieSource}>Source: {hit.source.toUpperCase()}</span>
)}
</div>
<div className={styles.movieActions}>
<button
onClick={() => onVote(hit.objectID)}
className={styles.voteButton}
>
👍 Vote ({hit.votes || 0})
</button>
</div>
</div>
</div>
);
};
// Empty results component
const EmptyResults = () => (
<div className={styles.emptyResults}>
<h3>No movies found</h3>
<p>Try a different search or add a new movie below</p>
</div>
);
export default function Paradiso() {
const router = useRouter();
const [userToken, setUserToken] = useState(null);
const [isSearching, setIsSearching] = useState(false);
const [newMovieTitle, setNewMovieTitle] = useState('');
const [isAdding, setIsAdding] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(null);
// Set user token on client-side
useEffect(() => {
setUserToken(getUserToken());
}, []);
// Function to vote for a movie
const handleVote = async (movieId) => {
if (!userToken) return;
try {
// Use Algolia's partial update to increment the votes counter
const response = await fetch('/api/paradiso/vote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
movieId,
userToken,
}),
});
if (!response.ok) {
throw new Error('Failed to vote for movie');
}
setSuccess('Vote recorded successfully!');
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
console.error('Error voting for movie:', err);
setError('Failed to vote for movie. Please try again.');
setTimeout(() => setError(null), 5000);
}
};
// Function to add a new movie
const handleAddMovie = async (e) => {
e.preventDefault();
if (!newMovieTitle.trim() || !userToken) return;
setIsAdding(true);
setError(null);
try {
// Add the movie directly by title
const addResponse = await fetch('/api/paradiso/add-movie', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: newMovieTitle,
userToken,
}),
});
if (!addResponse.ok) {
const errorData = await addResponse.json();
throw new Error(errorData.error || 'Failed to add movie');
}
setNewMovieTitle('');
setSuccess('Movie added successfully!');
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
console.error('Error adding movie:', err);
setError(err.message || 'Failed to add movie. Please try again.');
setTimeout(() => setError(null), 5000);
} finally {
setIsAdding(false);
}
};
return (
<Layout>
<Head>
<title>Paradiso - Movie Night Voting</title>
<meta name="description" content="Vote for the next movie night film" />
</Head>
<div className={styles.container}>
<h1 className={styles.title}>🎬 Paradiso</h1>
<p className={styles.subtitle}>Vote pour <b>notre</b> film et écris l'avenir du <i>Cinéma Paradiso</i>.</p>
{/* Error and success messages */}
{error && <div className={styles.errorMessage}>{error}</div>}
{success && <div className={styles.successMessage}>{success}</div>}
{/* InstantSearch component */}
{userToken && (
<InstantSearch
searchClient={searchClient}
indexName={process.env.NEXT_PUBLIC_ALGOLIA_INDEX}
>
<div className={styles.searchContainer}>
<SearchBox
className={styles.searchBox}
translations={{
placeholder: 'Search for movies...',
}}
onFocus={() => setIsSearching(true)}
onBlur={() => setTimeout(() => setIsSearching(false), 200)}
/>
<Configure
hitsPerPage={12}
distinct={true}
/>
{isSearching && (
<div className={styles.searchResults}>
<Hits
hitComponent={({ hit }) => (
<MovieHit hit={hit} onVote={handleVote} />
)}
classNames={{
list: styles.hitsList,
item: styles.hitItem,
empty: styles.noResults,
}}
emptyComponent={EmptyResults}
/>
</div>
)}
</div>
{/* Add movie form */}
<div className={styles.addMovieSection}>
<h2>Can't find the movie? Add it</h2>
<form onSubmit={handleAddMovie} className={styles.addMovieForm}>
<input
type="text"
value={newMovieTitle}
onChange={(e) => setNewMovieTitle(e.target.value)}
placeholder="Enter movie title..."
disabled={isAdding}
required
/>
<button
type="submit"
disabled={isAdding || !newMovieTitle.trim()}
>
{isAdding ? 'Adding...' : 'Add Movie'}
</button>
</form>
<p className={styles.infoText}>
Movie data is fetched from TMDB, OMDB, or Wikipedia as a fallback
</p>
</div>
{/* Top voted movies */}
<div className={styles.topMoviesSection}>
<h2>Top Voted Movies</h2>
<Configure
hitsPerPage={5}
filters="votes>0"
sortCriteria={['votes:desc', 'title:asc']}
/>
<Hits
hitComponent={({ hit }) => (
<MovieHit hit={hit} onVote={handleVote} />
)}
classNames={{
list: styles.hitsList,
item: styles.hitItem,
empty: styles.noResults,
}}
/>
</div>
</InstantSearch>
)}
</div>
<style jsx>{`
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.title {
font-size: 2.5rem;
text-align: center;
margin-bottom: 1rem;
}
.subtitle {
font-size: 1.2rem;
text-align: center;
margin-bottom: 2rem;
color: #666;
}
`}</style>
</Layout>
);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment