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/120 min 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/date combinations 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/Setting controllers and every Delete action
- AdminMutationAuditFilter logs every POST/PUT/DELETE in 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/App) determine 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.cshtml renders 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.
What to know before you buy
1 non-blocking finding shown publicly: 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. The seller has been notified; fixes ship in the next version.
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.