Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
3 changes: 3 additions & 0 deletions backend/backend/girls/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/backend/girls/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class GirlsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'backend.girls'
29 changes: 29 additions & 0 deletions backend/backend/girls/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.0.3 on 2025-03-15 07:55

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Girl',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('age', models.IntegerField(validators=[django.core.validators.MinValueValidator(18), django.core.validators.MaxValueValidator(70)])),
('bio', models.TextField(blank=True, null=True)),
('height', models.IntegerField(validators=[django.core.validators.MinValueValidator(120), django.core.validators.MaxValueValidator(200)])),
('skin_color', models.CharField(max_length=50)),
('hair_color', models.CharField(max_length=50)),
('eye_color', models.CharField(max_length=50)),
('image', models.URLField()),
],
),
]
Empty file.
23 changes: 23 additions & 0 deletions backend/backend/girls/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator


class Girl(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField(validators=[
MinValueValidator(18),
MaxValueValidator(70)
])
bio = models.TextField(blank=True, null=True)
height = models.IntegerField(validators=[
MinValueValidator(120),
MaxValueValidator(200)
])
skin_color = models.CharField(max_length=50)
hair_color = models.CharField(max_length=50)
eye_color = models.CharField(max_length=50)
image = models.URLField()


def __str__(self):
return self.name
8 changes: 8 additions & 0 deletions backend/backend/girls/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .models import Girl
from rest_framework import serializers


class GirlSerializer(serializers.ModelSerializer):
class Meta:
model = Girl
fields = '__all__'
3 changes: 3 additions & 0 deletions backend/backend/girls/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
12 changes: 12 additions & 0 deletions backend/backend/girls/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.urls import path
from .views import GirlViewSet, GirlRegisterView

urlpatterns = [
path('', GirlViewSet.as_view({'get': 'list'})),
path('create/', GirlRegisterView.as_view(), name='service_register'),
path('<str:pk>/', GirlViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
})),
]
22 changes: 22 additions & 0 deletions backend/backend/girls/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from .models import Girl
from .serializers import GirlSerializer
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser
from rest_framework.views import APIView


class GirlViewSet(viewsets.ModelViewSet):
queryset = Girl.objects.all()
serializer_class = GirlSerializer


class GirlRegisterView(APIView):
permission_classes = [IsAdminUser]

def post(self, request):
serializer = GirlSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
1 change: 1 addition & 0 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
'backend.services',
'backend.events',
'backend.escaperooms',
'backend.girls',
'rest_framework',
'corsheaders',
'storages',
Expand Down
1 change: 1 addition & 0 deletions backend/backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
path('api/services/', include('backend.services.urls')),
path('api/events/', include('backend.events.urls')),
path('api/escaperooms/', include('backend.escaperooms.urls')),
path('api/girls/', include('backend.girls.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='get_token'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='refresh_token'),
]
Expand Down
22 changes: 15 additions & 7 deletions frontend/public/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ hr {
text-transform: uppercase;
}

.bold {
font-weight: bold;
}

.flex {
display: flex;
}
Expand Down Expand Up @@ -275,26 +279,26 @@ hr {
}

/* Items */
.services .icon-row, .events .icon-row, .escape-rooms .icon-row {
.services .icon-row, .events .icon-row, .escape-rooms .icon-row, .girls .icon-row {
padding: 30px 0;
}

.escape-room {
.escape-room, .bet-zone-item, .girl {
margin-bottom: 3rem;
}

.service, .event, .escape-room {
.service, .event, .escape-room, .bet-zone-item, .girl {
width: 30%;
height: 100%;
}

.services-container, .events-container, .rooms-container {
.services-container, .events-container, .rooms-container, .bet-zone-container, .girls-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}

.escape-room p {
.escape-room p, .bet-zone-item p, .girl p {
display: -webkit-box;
-webkit-line-clamp: 6;
-webkit-box-orient: vertical;
Expand All @@ -306,6 +310,10 @@ hr {
cursor: pointer;
}

.girl .icon.icon.service-img {
height: unset;
}


/* cta */
.cta {
Expand Down Expand Up @@ -594,8 +602,8 @@ footer li a{
width: 90%;
}

.service, .event {
width: 100%;
.service, .event, .bet-zone-item, .escape-room {
width: 90%;
margin: 0 auto;
}

Expand Down
18 changes: 7 additions & 11 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import EscapeRooms from './pages/EscapeRooms'
import CreateEscapeRoom from './pages/CreateEscapeRoom'
import EditEscapeRoom from './pages/EditEscapeRoom'
import EscapeRoomDetails from './pages/EscapeRoomDetails'

import Girls from './pages/Girls';

import UnderConstruction from './pages/UnderConstruction'
import UserProfileView from './pages/UserProfieView'
import UserProfileEdit from './pages/UserProfileEdit'
import BetZone from './pages/BetZone'


import Header from './components/Header'
Expand Down Expand Up @@ -48,15 +52,7 @@ function RegisterAndLogout() {

// The user is logged out when the tab is closed
function App() {
// useEffect(() => {
// const handleBeforeUnload = () => {
// localStorage.clear();
// };
// window.addEventListener('beforeunload', handleBeforeUnload);
// return () => {
// window.removeEventListener('beforeunload', handleBeforeUnload);
// };
// }, []);


return (
<BrowserRouter>
Expand All @@ -82,9 +78,9 @@ function App() {
<Route path="/escape-rooms/edit/:escapeRoomId" element={<ProtectedRoute><EditEscapeRoom /></ProtectedRoute>} />
<Route path="/escape-rooms/:escapeRoomId" element={<ProtectedRoute><EscapeRoomDetails /></ProtectedRoute>} />
<Route path="/computer-club" element={<ProtectedRoute><UnderConstruction /></ProtectedRoute>} />
<Route path="/strip-club" element={<ProtectedRoute><UnderConstruction /></ProtectedRoute>} />
<Route path="/strip-club" element={<ProtectedRoute><Girls /></ProtectedRoute>} />
<Route path="/marketplace" element={<ProtectedRoute><UnderConstruction /></ProtectedRoute>} />
<Route path="/bet-zone" element={<ProtectedRoute><UnderConstruction /></ProtectedRoute>} />
<Route path="/bet-zone" element={<ProtectedRoute><BetZone /></ProtectedRoute>} />
<Route path="/user-profile" element={<ProtectedRoute><UserProfileView /></ProtectedRoute>} />
<Route path="/user-profile/edit" element={<ProtectedRoute><UserProfileEdit /></ProtectedRoute>} />

Expand Down
37 changes: 37 additions & 0 deletions frontend/src/components/Girl.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useNavigate, Link } from "react-router-dom"

import api from "../api"

export default function Girl({ girl }) {
const navigate = useNavigate();
const onDelete = async (id) => {

api.delete(`/api/girls/${id}/`)
.then(response => {
if (response.status === 204) {
console.log("Girl deleted successfully!");
navigate('/girls/', { replace: true });
} else {
console.log("Girl was not deleted!");
}
})
.catch(error => console.error(`Error: ${error}`));

}
return (
<div className="girl" id="girl">
<h3 className="section-title">{girl.name}</h3>
<img className="icon icon service-img" src={girl.image} onClick={() => navigate(`/girls/${girl.id}`)} alt={girl.name} />
<p>{girl.bio}</p>
<Link className='button' to={`/girls/${girl.id}`}>More</Link>
{
localStorage.getItem('admin') === 'true' && (
<>
<button className="delete-button" onClick={() => onDelete(girl.id)}>Delete</button>
<button className="edit-button" onClick={() => navigate(`/girls/edit/${girl.id}`)} >Edit</button>
</>
)
}
</div>
)
}
46 changes: 46 additions & 0 deletions frontend/src/pages/BetZone.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export default function BetZone() {
return (
<section className="bet-zone" id="bet-zone">
<div className="container">
<div className="row">
<h2 className="section-title">Bet Zone</h2>
</div>
<div className="center-xs bet-zone-container">
<div className="bet-zone-item">
<img className="icon icon service-img" src="https://static.independent.co.uk/2024/11/13/12/how-to-play-poker-copy.jpg" alt="Poker" />
<h3>Poker</h3>
<p>Think you’ve got the skills to bluff, bet, and dominate the table? Come and see if you're good enough. </p>
<div className="bold">
<p>💰 Big Wins. Bigger Thrills.</p>
<p>🏆 Daily Tournaments & High-Stakes Action</p>
<p>🎁 Exclusive Bonuses for New Players</p>
</div>
<p>The cards are shuffled. The chips are stacked. Are you in?</p>
</div>
<div className="bet-zone-item">
<img className="icon icon service-img" src="https://www.footballtipster.net/blog/wp-content/uploads/2024/07/Betting-%E2%80%93-Ways-to-make-money-and-certain-restrictions-football-1-930x608.jpg" alt="Football" />
<h3>Football</h3>
<p>The roar of the crowd. The thrill of the goal. The most beautiful game is calling—are you ready?</p>
<div className="bold">
<p>⚽ Live Matches & Exclusive Coverage</p>
<p>🔥 Unstoppable Action, Every Game</p>
<p>🎟️ VIP Access & Fan Giveaways</p>
</div>
<p>This is more than a game. It’s a way of life. Join the action now!</p>
</div>
<div className="bet-zone-item">
<img className="icon icon service-img" src="https://betravingknows.com/wp-content/uploads/2019/07/slot-machines-gaming-floor_m.jpg" alt="Slot Machines" />
<h3>Slot Machines</h3>
<p>More than 150 slot machines are waiting for you. Step up to the reels and feel the thrill of huge wins, nonstop action, and massive jackpots!</p>
<div className="bold">
<p>🎰 Exciting Slot Games & Big Payouts</p>
<p>🔥 Daily Bonuses & Free Spins</p>
<p>💎 Jackpots Waiting to Be Claimed</p>
</div>
<p>The reels are spinning… Will you hit the jackpot?</p>
</div>
</div>
</div>
</section>
)
}
30 changes: 15 additions & 15 deletions frontend/src/pages/EscapeRooms.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ export default function EscapeRooms() {
const [displayedRooms, setDisplayedRooms] = useState([]);
const ITEMS_PER_PAGE = 6;
const [searchParams] = useSearchParams();


useEffect(() => {
getEscapeRooms();
}, [escapeRooms]);

const getEscapeRooms = async () => {
api.get("/api/escaperooms/")
.then((response) => {
const p = [];
for (let i = 0; i < Math.ceil(response.data.length / ITEMS_PER_PAGE); i++) {
p.push(i + 1);
}
setPages(p);
setEscapeRooms(response.data);
})
.catch((error) => console.error(`Error: ${error}`));;
.then((response) => {
const p = [];
for (let i = 0; i < Math.ceil(response.data.length / ITEMS_PER_PAGE); i++) {
p.push(i + 1);
}
setPages(p);
setEscapeRooms(response.data);
})
.catch((error) => console.error(`Error: ${error}`));;
}

useEffect(() => {
const page = parseInt(searchParams.get('page')) || 1;
const startIndex = (page - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;

setDisplayedRooms(escapeRooms.slice(startIndex, endIndex));
}, [escapeRooms, searchParams]);
}, [escapeRooms, searchParams]);


return (
Expand All @@ -43,8 +43,8 @@ export default function EscapeRooms() {
<div className="row">
<h2 className="section-title">Escape Rooms</h2>
</div>
<div className="rooms-container">
{displayedRooms.map((escapeRoom, index) => <EscapeRoom key={index} escapeRoom={escapeRoom} />) }
<div className="center-xs rooms-container">
{displayedRooms.map((escapeRoom, index) => <EscapeRoom key={index} escapeRoom={escapeRoom} />)}
</div>
{
localStorage.getItem('admin') === 'true' && (
Expand All @@ -59,5 +59,5 @@ export default function EscapeRooms() {
</div>
</section>

)
)
}
Loading