Why Padel Nation
What makes it better
than the alternatives
- 01
Four surfaces from one solution
Four surfaces from one solution
- 02
Split-payment invitations
Split-payment invitations
- 03
Interactive calendar booking
Interactive calendar booking
- 04
Server-side price calculation
Server-side price calculation
- 05
Comprehensive audit and ownership enforcement
Comprehensive audit and ownership enforcement
Venue & Court Management
- Padel entity (venue) with name
- description
- logo
- banner
- address
- city
- country
- status
- PadelBranch entity (location) with branch name
- address
- coordinates
- gallery images
- facilities
- opening hours
- Court entity with name
- type
- surface
- pricing
30/60/90/120min rates) - status
- branch assignment
- BookingSchedule entity defining time slots per branch with start/end times
- active flag
- day-of-week support
- Image upload for venue logos
- branch galleries and court photos (ImageUploader
- UploadFolders.Branches
- max 5MB)
- Admin screens: Padel/Index (venue list)
- Padel/Edit (venue form)
- PadelBranch/Index (branch list with venue filter)
- PadelBranch/Edit (branch form with gallery)
- Court/Index (court list)
- Court/Edit (court form with pricing)
Booking & Availability
- Invoice entity (booking record) with readable ID
- member
- court
- date
- time
- duration
- status
Pending/Confirmed/Completed/Cancelled - grand total
- due payment
- deposit percentage
- InvoiceTimeSchedule join table linking invoices to
court/schedule/datecombinations for multi-slot bookings - BookingService.CreateBookingAsync recalculates price from court rates and duration
- applies coupon discounts
- creates pending-payment shares for invited players
- BookingService.GetAvailabilityAsync queries booked slots per court/date and returns free time windows
- BookingService.CancelBookingAsync (member-scoped) and CancelBookingAsAdminAsync (staff) soft-delete invoices and log cancellation comments
- API endpoints: POST /
api/v1/bookings(create) - GET /
api/v1/bookings(list owner's bookings) - POST /
api/v1/bookings/{id}/cancel (member cancellation) - Admin screens: Booking/Index (list with status tabs:
pending/confirmed/completed/cancelled - Booking/Details (invoice detail with players)
- Calendar/Index (interactive day-view grid)
Member Authentication & Sessions
- Member entity with phone (unique)
- full name
- password hash
- security stamp
- IsRegistered flag
- status
Active/Inactive/Suspended - city
- country
- picture
- OTP code/expiry
- AuthService.MemberRegisterAsync validates phone format and password policy
- creates member with hashed password
- issues JWT token
- AuthService.MemberLoginAsync verifies phone/password
- checks account status
- returns MemberSessionDto with token and expiry
- JwtTokenService issues bearer tokens with member ID
- phone
- security stamp claims; tokens expire after 720 hours (configurable Jwt:ExpiryHours)
- ApiControllerBase.CurrentMemberId extracts member ID from JWT claims; every API action inherits Member policy (bearer token required)
- gp-auth.js client session manager stores token in localStorage
- auto-attaches Authorization header to /api/v1 calls
- clears session on 401 and fires padelnation:signed-out event
- API endpoints: POST /
api/v1/members/register - POST /
api/v1/members/login - GET /
api/v1/members/me(token validation)
Split Payments & Invitations
- PendingPayment entity with invoice
- member
- amount
- status
- payment date
- created/updated timestamps
- BookingService.CreateBookingAsync accepts invitedPlayerIds array
- calculates per-player share (grand total ÷ player count)
- creates PendingPayment records for each invited player
- AuthService.ResolveMemberByPhoneAsync creates unregistered member stub (no password
- IsRegistered=false) if phone not found
- so invitations work before invitee registers
- AuthService.SuggestContactsAsync returns type-ahead results limited to requester's shared bookings/tournaments plus exact phone match (prevents directory browsing)
- API rate limit: member-resolve endpoint limited to 10 calls per member per hour (RateLimitPolicies.MemberResolve
- partitioned by member ID)
- Member/Details admin screen shows PendingShares list (payments owed by this member as invited player)
Coupons & Discounts
- Coupon entity with code (unique)
- title
- description
- discount type (Percentage/FixedAmount)
- discount value
- start/end dates
- usage limit
- used count
- status
- CouponService.ValidateCouponAsync checks code existence
- active status
- date range
- usage limit
- returns discount amount
- BookingService.CreateBookingAsync applies coupon discount to subtotal before calculating grand total and deposit
- Admin screens: Coupon/Index (list with tabs:
active/scheduled/expired/inactive - Coupon/Edit (form with date pickers and discount type selector)
- API endpoint: GET /
api/v1/coupons/validate?code=X returns discount details or error message
Tournaments
- Tournament entity with name
- description
- image
- start/end dates
- registration deadline
- max teams
- entry fee
- prize pool
- status
- branch assignment
- TournamentTeam entity with tournament
- team name
- captain member
- status
- registration date
- TournamentTeamMember join table linking teams to member rosters
- TournamentStage entity (groups
- knockout rounds) with stage name
- type
- order
- TournamentFixture entity with stage
- home/away teams
- match date/time
- court
- score
- status
Scheduled/InProgress/Completed/Cancelled - TournamentService.RegisterTeamAsync validates registration deadline
- team limit
- creates team and roster entries
- TournamentService.GetTournamentDetailsAsync returns tournament with stages
- fixtures
- teams and results
- Admin screens: Tournament/Index (list)
- Tournament/Edit (form with image upload)
- Tournament/Details
stages/fixtures/teams - API endpoints: GET /
api/v1/tournaments(list) - GET /
api/v1/tournaments/{id} (detail) - POST /
api/v1/tournaments/{id}/register (team registration)
Admin Console & Roles
- User entity with username
- full name
- password hash
- role
- IsAdminUser flag
- status
- Role entity with name
- permissions JSON (not enforced in code
- placeholder for future granular permissions)
- AdminArea policy requires Admin or Staff role (cookie authentication
- CookieAuthenticationDefaults.AuthenticationScheme)
- AdminOnly policy requires Admin role; applied to
User/Role/Settingcontrollers and every Delete action - AdminMutationAuditFilter logs every
POST/PUT/DELETEin Admin area to AuditLog table (user ID - action
- controller
- timestamp
- IP address)
- Admin screens: Dashboard/Index (stats cards
- recent bookings
- revenue trend
- status breakdown)
- User/Index (user list)
- User/Edit (user form)
- Role/Index (role list)
- Role/Edit (role form)
- AuthController.Login (POST with rate limit)
- AuthController.Logout (POST with anti-forgery token)
Reports & Analytics
- DashboardService.GetStatsAsync returns total bookings
- revenue
- active members
- active courts for current month
- DashboardService.GetRevenueTrendAsync returns daily revenue for last N days
- ReportService.GetSalesReportAsync (date range
- branch filter) returns bookings with invoice details
- member names
- payment status
- ReportService.GetCommissionReportAsync calculates venue/branch commission from GrandTotal and CommissionPercentage
- ReportService.GetPadelCalendarReportAsync returns bookings per court/date for calendar heatmap
- ReportService.GetProductReportAsync groups bookings by court and sums revenue
- ReportService.GetTrainerReportAsync groups bookings by trainer (if assigned) and sums sessions/revenue
- ReportService.GetPaymentMethodReportAsync groups invoices by payment method and sums totals
- Admin screens: Report/Sales
- Report/Commission
- Report/PadelCalendar
- Report/Products
- Report/Trainers
- Report/PaymentMethods (all with date/branch filters and export buttons)
Password Reset & Email
- PasswordResetService.RequestAsync generates single-use token (HMACSHA256 of account kind
- ID
- password hash
- expiry)
- sends email with reset link
- PasswordResetService.ResetConfirmedAsync validates token
- checks expiry (default 60 minutes)
- hashes new password
- updates account
- invalidates token by changing security stamp
- SmtpEmailSender sends emails via configured SMTP (host
- port
- secure flag
- user
- password
- from address in appsettings.json or environment variables)
- Three surfaces:
Admin/Auth/ForgotPassword(admin users) - Website/MemberPassword (website members)
- App password-reset screen (PWA members)
- PasswordResetAccountKind enum (Admin/Member) and PasswordResetSurface enum
Admin/Website/Appdetermine link destination - Rate limit: password-reset endpoints limited to 5 requests per IP per 15 minutes (RateLimitPolicies.PasswordReset)
- API endpoints: POST /
api/v1/members/forgot-password - GET /
api/v1/members/reset-password/validate?token=X - POST /
api/v1/members/reset-password
Progressive Web App
- manifest.webmanifest with app name
- icons (192×192
- 512×512)
- start_url=/app
- display=standalone
- theme/background colors
- Service worker (sw.js) caches app shell (HTML
- CSS
- JS
- icons) for offline access
- serves cached shell on network failure
- offline.html fallback page shown when network unavailable and requested page not cached
App/Index.cshtmlrenders mobile-first booking interface with venue search- availability grid
- booking form
- gp-auth.js session manager works identically in website and PWA (same localStorage key
- same JWT token)
- Installable on iOS (Add to Home Screen) and Android (install prompt)
| Language | Version | Framework | Used for | Share of code |
|---|---|---|---|---|
| C# | — | — | Web, API, admin | 35.1% |
| C# (Razor) | — | — | Web pages | 22.3% |
| JavaScript | — | — | Front-end | 17.7% |
| SQL | — | — | Database | 13.1% |
| CSS | — | — | Styling | 11.4% |
| PowerShell | — | — | Scripts | 0.3% |
| HTML | — | — | Front-end | 0.1% |
- Primary language
- C# 12 (.NET 8)
- Framework
- ASP.NET Core 8.0
- Database
- SQL Server 2019
- Authentication
- Cookie (Admin), JWT Bearer (API/Member)
- Licence
- Single-site
- Screens
- 47
- Database tables
- 37
- Supported languages
- English only
- Browser support
- Modern browsers (Chrome, Firefox, Safari, Edge); PWA installable on iOS/Android
| Minimum | Recommended | |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 4 GB RAM / 20 GB SSD | 8 GB RAM / 40 GB SSD |
| OS | Linux/Windows with .NET 8 SDK 8.0.100+, SQL Server 2019+ or SQL Server Express | Linux/Windows with .NET 8 SDK 8.0.100+, SQL Server 2019+ or SQL Server Express |
| Est. monthly hosting | $25 | $25 |
```bash cp .env.example .env # Edit .env: set SA_PASSWORD, JWT_KEY, ADMIN_BOOTSTRAP_* (see .env.example) docker compose up --build -d # App: http://localhost:8080 Admin: http://localhost:8080/Admin ```
```bash # Install .NET 8 SDK wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh chmod +x dotnet-install.sh ./dotnet-install.sh --channel 8.0 export PATH="$HOME/.dotnet:$PATH" # Install SQL Server (Ubuntu example) wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list)" sudo apt-get update sudo apt-get install -y mssql-server sudo /opt/mssql/bin/mssql-conf setup # Choose edition, set SA password # Build and run git clone <repo> cd GoPadelPro dotnet restore GoPadelPro.sln --locked-mode dotnet build GoPadelPro.sln -c Release --no-restore # Set environment variables (or appsettings.Production.json) export ConnectionStrings__Default="Server=localhost;Database=GoPadelPro;User Id=sa;Password=<SA_PASSWORD>;TrustServerCertificate=True;MultipleActiveResultSets=True" export Jwt__Key="<32_CHAR_KEY>" export AdminBootstrap__UserName="admin" export AdminBootstrap__Email="admin@example.com" export AdminBootstrap__Password="<STRONG_PASSWORD>" export App__PublicBaseUrl="https://yourdomain.com" cd src/GoPadelPro.Web dotnet run -c Release --no-build --urls "http://0.0.0.0:5000" # Reverse proxy with nginx/caddy for HTTPS ```
```bash # Create resources az group create --name padelnation-rg --location eastus az sql server create --name padelnation-sql --resource-group padelnation-rg --location eastus --admin-user sqladmin --admin-password <STRONG_PASSWORD> az sql db create --resource-group padelnation-rg --server padelnation-sql --name GoPadelPro --service-objective S1 az webapp create --resource-group padelnation-rg --plan padelnation-plan --name padelnation-app --runtime "DOTNETCORE:8.0" # Configure app settings az webapp config appsettings set --resource-group padelnation-rg --name padelnation-app --settings \ ConnectionStrings__Default="Server=padelnation-sql.database.windows.net;Database=GoPadelPro;User Id=sqladmin;Password=<PASSWORD>;" \ Jwt__Key="<32_CHAR_KEY>" \ AdminBootstrap__UserName="admin" \ AdminBootstrap__Email="admin@example.com" \ AdminBootstrap__Password="<STRONG_PASSWORD>" \ App__PublicBaseUrl="https://padelnation-app.azurewebsites.net" # Deploy dotnet publish src/GoPadelPro.Web/GoPadelPro.Web.csproj -c Release -o ./publish cd publish zip -r ../app.zip . az webapp deployment source config-zip --resource-group padelnation-rg --name padelnation-app --src ../app.zip ```
```bash # Install SQL Server Express (Windows) or Docker SQL Server (Mac/Linux) # Windows: download from microsoft.com/sql-server/sql-server-downloads # Mac/Linux: docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Dev123!@#" -p 1433:1433 --name sqlserver -d mcr.microsoft.com/mssql/server:2019-latest # Clone and restore git clone <repo> cd GoPadelPro dotnet restore GoPadelPro.sln --locked-mode # Configure (create appsettings.local.json or use user-secrets) cd src/GoPadelPro.Web dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=GoPadelPro;User Id=sa;Password=Dev123!@#;TrustServerCertificate=True;MultipleActiveResultSets=True" dotnet user-secrets set "Jwt:Key" "dev-key-32-characters-minimum-length-required" dotnet user-secrets set "AdminBootstrap:UserName" "admin" dotnet user-secrets set "AdminBootstrap:Email" "admin@local.dev" dotnet user-secrets set "AdminBootstrap:Password" "Admin123!@#" # Run dotnet run # Website: https://localhost:5001 Admin: https://localhost:5001/Admin # API: https://localhost:5001/api/v1 ```
Quality report
Manually reviewedHow to read the quality report
What this is. Every project on Vibe96 goes through an automated audit of its source code before a reviewer in Dubai checks it by hand. The score is out of 100; 75 is the minimum to be listed, and any single failing check blocks a listing regardless of the score.
- Security
- No real credentials in the code, every data-changing route protected, admin areas role-gated, no SQL built from user input.
- Code structure
- A developer can navigate and extend it: clear module boundaries, no giant files, little copy-paste.
- Docs & completeness
- The setup guide, example config, schema and required files the builder committed to are actually in the archive.
- Dependency health
- Packages pinned, no known critical vulnerabilities, no abandoned libraries in production paths.
- Build & deploy
- A clean machine can restore, build and start it by following the shipped instructions.
Warnings are problems that didn't block the listing; they stay visible here until a later version clears them. The audit reads the code — it does not run it — so always try the live demo before you buy. Lines of code are counted from the same audited archive.
Reviewer notes (1 minor)
Weak default jwt key and sql password in example files; no real credentials found; strong authentication and role guards; missing ownership checks on member reads; moderate code duplication and some oversized files; structure generally sound; multiple unindexed foreign keys, missing where clauses, and schema gaps. None of these stop the software from working; the developer has been told and improvements come in a future update.
Versions & updates
No updates yet. New versions appear here with a changelog.
Buyers receive updates free for 1 year.
What you get
- </>Full source codeThe complete audited source, delivered as a download from your account. No obfuscation.
- ▤DocumentationSetup guide, architecture notes, CLAUDE.md and AGENTS.md.
- ↻One year of updatesEvery new version the builder publishes, free for 12 months.
- ♧30 days of supportInstallation and defect support through Vibe96 tickets.