Apex Fitness

Next.jsReactTypeScriptTailwind CSSSCSSPrismaPostgreSQLNeonAuAuth.jsStripeResendRCRechartsNTnext-themesGoogle MapsQRQRCodeZXZXingECEmbla CarouselBootstrap IconsbcbcryptjsVercel

Features

14 shipped4 in progress

Shipped14

  • Abonamente
  • Admin Dashboard
  • Analytics Admin
  • Auth
  • Badges & Achievements
  • Cos
  • Dark Mode
  • Footer
  • Galerie
  • Home
  • Locatie
  • Navbar
  • QR Acces
  • Statistici Membri

In Progress4

  • Antrenori
  • Booking Sesiuni
  • Locatii
  • Profil

Database schema

User11 fields
idString (cuid)
nameString
emailString
emailVerifiedDateTime
imageString
passwordString

Utilizator principal. role: MEMBER | TRAINER | ADMIN | HR_MANAGER. Hub central — toate entitățile au relație cu User.

Account13 fields
userIdString → User
providerString
providerAccountIdString
typeString
refresh_tokenString
access_tokenString

Conturi OAuth legate la User (Google etc.). PK compus: [provider, providerAccountId].

Session5 fields
sessionTokenString (unique)
userIdString → User
expiresDateTime
createdAtDateTime
updatedAtDateTime

Sesiuni NextAuth active. Token unic per sesiune de browser.

VerificationToken3 fields
identifierString
tokenString
expiresDateTime

Token pentru verificare email. PK compus: [identifier, token].

PasswordResetToken5 fields
idString (cuid)
tokenString (cuid, unique)
userIdString → User
expiresDateTime
createdAtDateTime

Token de reset parolă generat la cerere. Un utilizator poate avea mai multe tokene active.

SubscriptionCategory6 fields
idInt (PK)
slugString (unique)
nameString
imageUrlString
isActiveBoolean
sortOrderInt

Categorii de abonament: fitness, zumba, aerobic, cycling. slug unic pentru URL.

SubscriptionPlan13 fields
idInt (PK)
categoryIdInt → SubscriptionCategory
nameString
planTypePlanType
entriesInt
durationMonthsInt

Plan individual dintr-o categorie. planType: ENTRIES (nr intrări) | MONTHLY (lunar). Legat la OrderItem.

DiscountCode10 fields
idInt (PK)
codeString (unique)
discountPctDecimal(5,2)
discountRonDecimal(10,2)
validFromDateTime
validUntilDateTime

Cod promoțional aplicabil la checkout. Reducere fie procentuală, fie sumă fixă. Referit din Order.discountCode.

Order10 fields
idInt (PK)
userIdString → User
statusOrderStatus
totalRonDecimal(10,2)
discountCodeString → DiscountCode
discountRonDecimal(10,2)

Comandă de cumpărare. status: PENDING | PAID | CANCELLED | EXPIRED. Conține unul sau mai multe OrderItem.

OrderItem10 fields
idInt (PK)
orderIdInt → Order
planIdInt → SubscriptionPlan
planNameString
categoryNameString
quantityInt

Linie din comandă. planName/categoryName salvate denormalizat pentru istoric. CheckIn-urile consumă intrările din acest item.

CheckIn5 fields
idInt (PK)
orderItemIdInt → OrderItem
userIdString → User
scannedByIdString → User
scannedAtDateTime

Intrare QR la sală. scannedById = staff care a scanat codul. Consumă o intrare din OrderItem.

Location17 fields
idInt (PK)
slugString (unique)
nameString
cityString
countyString
postalCodeString

Locație fizică a sălii. schedule = JSON cu orele de funcționare. amenities = dotări disponibile.

Trainer20 fields
idInt (PK)
nameString
ageInt
categoryString
classesString[]
specializationsString[]

Profil antrenor. Poate fi legat la un cont User (userId unique). Aparține unei locații.

TrainerReview6 fields
idInt (PK)
trainerIdInt → Trainer
userIdString → User
ratingInt
commentString
createdAtDateTime

Recenzie lăsată de un membru pentru un antrenor. Unique pe (trainerId, userId) — un singur review per pereche.

ClassSession11 fields
idInt (PK)
trainerIdInt → Trainer
locationIdInt → Location
categorySlugString
startAtDateTime
durationMinutesInt

Sesiune de clasă de grup programată. status: SCHEDULED | CANCELLED | COMPLETED. Membrii se înscriu prin ClassBooking.

ClassBooking5 fields
idInt (PK)
sessionIdInt → ClassSession
userIdString → User
statusBookingStatus
createdAtDateTime

Rezervare a unui User la o ClassSession. status: CONFIRMED | CANCELLED. Unique pe (sessionId, userId).

PersonalRequest9 fields
idInt (PK)
memberIdString → User
trainerIdInt → Trainer
requestedAtDateTime
messageString
statusRequestStatus

Cerere pentru ședință personală cu un antrenor. status: PENDING | ACCEPTED | REJECTED | CANCELLED.

CorporateAccount9 fields
idString (cuid)
companyNameString
seatsTotalInt
seatsUsedInt
stripeSubscriptionIdString
statusCorporateStatus

Cont corporativ B2B. hrManagerId unique — un singur HR Manager per companie. status: ACTIVE | SUSPENDED | CANCELLED.

CorporateMember5 fields
idString (cuid)
corporateAccountIdString → CorporateAccount
userIdString → User
joinedAtDateTime
revokedAtDateTime

Angajat activ într-un CorporateAccount. Unique pe (corporateAccountId, userId). revokedAt setat la revocare.

CorporateInvite7 fields
idString (cuid)
corporateAccountIdString → CorporateAccount
emailString
tokenString (cuid, unique)
expiresAtDateTime
acceptedAtDateTime

Invitație trimisă pe email unui angajat. Acceptată la /corporate/invite/[token]. acceptedAt null = în așteptare.

ClientProfile15 fields
idString (cuid)
userIdString → User (unique)
reasonString
injuriesString
medicalRestrictionsString
fitnessGoalString

Profil creat prin AI onboarding chat. Un profil per User. rawConversation = istoricul complet al conversației cu AI.

AIQuizResult11 fields
idString (cuid)
userIdString → User
sessionIdString
budgetInt
frequencyString
activityString

Rezultat quiz AI pentru recomandare abonament. userId opțional (și utilizatori neautentificați pot da quiz). convertedAt = cumpărat ulterior.

AIRetentionLog5 fields
idString (cuid)
userIdString → User
triggerTypeString
sentAtDateTime
emailSubjectString

Log email de retenție trimis automat de AI. Unique pe (userId, triggerType, sentAt) pentru a preveni spam.

UserBadge4 fields
idInt (PK)
userIdString → User
badgeIdString
unlockedAtDateTime

Badge/achievement deblocat de un utilizator. badgeId referă definițiile din codul aplicației (IBadgeDefinition). Unique pe (userId, badgeId).

GalleryPhoto7 fields
idInt (PK)
filenameString
urlString
altTextString
sortOrderInt
isActiveBoolean

Fotografie din galeria sălii. url = cale publică în /public/gallery/. Afișată în pagina /galerie cu modal intercepting route.