diff --git a/backend/backend/girls/__init__.py b/backend/backend/girls/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend/girls/admin.py b/backend/backend/girls/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/backend/girls/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/backend/girls/apps.py b/backend/backend/girls/apps.py new file mode 100644 index 0000000..f0d2ca0 --- /dev/null +++ b/backend/backend/girls/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GirlsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'backend.girls' diff --git a/backend/backend/girls/migrations/0001_initial.py b/backend/backend/girls/migrations/0001_initial.py new file mode 100644 index 0000000..777d39b --- /dev/null +++ b/backend/backend/girls/migrations/0001_initial.py @@ -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()), + ], + ), + ] diff --git a/backend/backend/girls/migrations/__init__.py b/backend/backend/girls/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend/girls/models.py b/backend/backend/girls/models.py new file mode 100644 index 0000000..dc222cf --- /dev/null +++ b/backend/backend/girls/models.py @@ -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 diff --git a/backend/backend/girls/serializers.py b/backend/backend/girls/serializers.py new file mode 100644 index 0000000..bec9dbf --- /dev/null +++ b/backend/backend/girls/serializers.py @@ -0,0 +1,8 @@ +from .models import Girl +from rest_framework import serializers + + +class GirlSerializer(serializers.ModelSerializer): + class Meta: + model = Girl + fields = '__all__' \ No newline at end of file diff --git a/backend/backend/girls/tests.py b/backend/backend/girls/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/backend/girls/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/backend/girls/urls.py b/backend/backend/girls/urls.py new file mode 100644 index 0000000..b5dcb5d --- /dev/null +++ b/backend/backend/girls/urls.py @@ -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('/', GirlViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'delete': 'destroy' + })), +] \ No newline at end of file diff --git a/backend/backend/girls/views.py b/backend/backend/girls/views.py new file mode 100644 index 0000000..d0b7361 --- /dev/null +++ b/backend/backend/girls/views.py @@ -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) diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 4ef05ca..dca0de5 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -67,6 +67,7 @@ 'backend.services', 'backend.events', 'backend.escaperooms', + 'backend.girls', 'rest_framework', 'corsheaders', 'storages', diff --git a/backend/backend/urls.py b/backend/backend/urls.py index f2e1501..0adf908 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -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'), ] diff --git a/frontend/public/styles/theme.css b/frontend/public/styles/theme.css index 48588b9..5fcabbf 100644 --- a/frontend/public/styles/theme.css +++ b/frontend/public/styles/theme.css @@ -116,6 +116,10 @@ hr { text-transform: uppercase; } +.bold { + font-weight: bold; +} + .flex { display: flex; } @@ -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; @@ -306,6 +310,10 @@ hr { cursor: pointer; } +.girl .icon.icon.service-img { + height: unset; +} + /* cta */ .cta { @@ -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; } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8efc0d6..717cbde 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -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' @@ -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 ( @@ -82,9 +78,9 @@ function App() { } /> } /> } /> - } /> + } /> } /> - } /> + } /> } /> } /> diff --git a/frontend/src/components/Girl.jsx b/frontend/src/components/Girl.jsx new file mode 100644 index 0000000..2226df8 --- /dev/null +++ b/frontend/src/components/Girl.jsx @@ -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 ( +
+

{girl.name}

+ navigate(`/girls/${girl.id}`)} alt={girl.name} /> +

{girl.bio}

+ More + { + localStorage.getItem('admin') === 'true' && ( + <> + + + + ) + } +
+ ) +} \ No newline at end of file diff --git a/frontend/src/pages/BetZone.jsx b/frontend/src/pages/BetZone.jsx new file mode 100644 index 0000000..5e8c938 --- /dev/null +++ b/frontend/src/pages/BetZone.jsx @@ -0,0 +1,46 @@ +export default function BetZone() { + return ( +
+
+
+

Bet Zone

+
+
+
+ Poker +

Poker

+

Think you’ve got the skills to bluff, bet, and dominate the table? Come and see if you're good enough.

+
+

💰 Big Wins. Bigger Thrills.

+

🏆 Daily Tournaments & High-Stakes Action

+

🎁 Exclusive Bonuses for New Players

+
+

The cards are shuffled. The chips are stacked. Are you in?

+
+
+ Football +

Football

+

The roar of the crowd. The thrill of the goal. The most beautiful game is calling—are you ready?

+
+

⚽ Live Matches & Exclusive Coverage

+

🔥 Unstoppable Action, Every Game

+

🎟️ VIP Access & Fan Giveaways

+
+

This is more than a game. It’s a way of life. Join the action now!

+
+
+ Slot Machines +

Slot Machines

+

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!

+
+

🎰 Exciting Slot Games & Big Payouts

+

🔥 Daily Bonuses & Free Spins

+

💎 Jackpots Waiting to Be Claimed

+
+

The reels are spinning… Will you hit the jackpot?

+
+
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/pages/EscapeRooms.jsx b/frontend/src/pages/EscapeRooms.jsx index b891413..32314af 100644 --- a/frontend/src/pages/EscapeRooms.jsx +++ b/frontend/src/pages/EscapeRooms.jsx @@ -9,7 +9,7 @@ export default function EscapeRooms() { const [displayedRooms, setDisplayedRooms] = useState([]); const ITEMS_PER_PAGE = 6; const [searchParams] = useSearchParams(); - + useEffect(() => { getEscapeRooms(); @@ -17,24 +17,24 @@ export default function 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 ( @@ -43,8 +43,8 @@ export default function EscapeRooms() {

Escape Rooms

-
- {displayedRooms.map((escapeRoom, index) => ) } +
+ {displayedRooms.map((escapeRoom, index) => )}
{ localStorage.getItem('admin') === 'true' && ( @@ -59,5 +59,5 @@ export default function EscapeRooms() {
- ) + ) } \ No newline at end of file diff --git a/frontend/src/pages/Girls.jsx b/frontend/src/pages/Girls.jsx new file mode 100644 index 0000000..af1ea6c --- /dev/null +++ b/frontend/src/pages/Girls.jsx @@ -0,0 +1,62 @@ +import Girl from "../components/Girl" +import React, { useState, useEffect } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import api from '../api'; + +export default function Girls() { + const [Girls, setGirls] = useState([]); + const [pages, setPages] = useState([]); + const [displayedGirls, setDisplayedGirls] = useState([]); + const ITEMS_PER_PAGE = 6; + const [searchParams] = useSearchParams(); + + + useEffect(() => { + getGirls(); + }, [Girls]); + + const getGirls = async () => { + api.get("/api/girls/") + .then((response) => { + const p = []; + for (let i = 0; i < Math.ceil(response.data.length / ITEMS_PER_PAGE); i++) { + p.push(i + 1); + } + setPages(p); + setGirls(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; + setDisplayedGirls(Girls.slice(startIndex, endIndex)); + }, [Girls, searchParams]); + + + return ( +
+
+
+

Girls

+
+
+ {displayedGirls.map((girl, index) => )} +
+ { + localStorage.getItem('admin') === 'true' && ( +
+ Add Girl +
+ ) + } +
+
+ {pages.map((page, index) => {page})} +
+
+ + ) +} \ No newline at end of file diff --git a/frontend/src/pages/Services.jsx b/frontend/src/pages/Services.jsx index 78e6427..843f6d6 100644 --- a/frontend/src/pages/Services.jsx +++ b/frontend/src/pages/Services.jsx @@ -27,7 +27,7 @@ export default function Services() {

Services

-
+
{services.map((service, index) => ) }
{