feat: basic initial UI layout for mobile and desktop

This commit is contained in:
Jose Selesan
2026-09-07 09:33:07 -03:00
parent 0aa5d70101
commit b41fffa40a
46 changed files with 2102 additions and 125 deletions

View File

@@ -0,0 +1,191 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
-- CreateEnum
CREATE TYPE "PaymentStatus" AS ENUM ('PENDING', 'PAID', 'OVERDUE', 'CANCELLED');
-- CreateEnum
CREATE TYPE "WaitlistStatus" AS ENUM ('PENDING', 'INVITED', 'JOINED', 'DECLINED');
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
"image" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "accounts" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refreshToken" TEXT,
"accessToken" TEXT,
"accessTokenExpiresAt" TIMESTAMP(3),
"refreshTokenExpiresAt" TIMESTAMP(3),
"scope" TEXT,
"idToken" TEXT,
"password" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sessions" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"ipAddress" TEXT,
"userAgent" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "verifications" (
"id" TEXT NOT NULL,
"identifier" TEXT NOT NULL,
"value" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "verifications_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "groups" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"createdById" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "groups_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "members" (
"id" TEXT NOT NULL,
"groupId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'MEMBER',
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "members_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "students" (
"id" TEXT NOT NULL,
"groupId" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"email" TEXT,
"phone" TEXT,
"guardianName" TEXT,
"guardianPhone" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "students_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "payments" (
"id" TEXT NOT NULL,
"groupId" TEXT NOT NULL,
"studentId" TEXT NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'MXN',
"status" "PaymentStatus" NOT NULL DEFAULT 'PENDING',
"dueDate" TIMESTAMP(3) NOT NULL,
"paidAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "payments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "waitlist_entries" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"status" "WaitlistStatus" NOT NULL DEFAULT 'PENDING',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT,
CONSTRAINT "waitlist_entries_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "accounts_providerId_providerAccountId_key" ON "accounts"("providerId", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "sessions_token_key" ON "sessions"("token");
-- CreateIndex
CREATE UNIQUE INDEX "verifications_identifier_value_key" ON "verifications"("identifier", "value");
-- CreateIndex
CREATE UNIQUE INDEX "members_groupId_userId_key" ON "members"("groupId", "userId");
-- CreateIndex
CREATE INDEX "students_groupId_idx" ON "students"("groupId");
-- CreateIndex
CREATE INDEX "payments_groupId_idx" ON "payments"("groupId");
-- CreateIndex
CREATE INDEX "payments_studentId_idx" ON "payments"("studentId");
-- CreateIndex
CREATE UNIQUE INDEX "waitlist_entries_userId_key" ON "waitlist_entries"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "waitlist_entries_email_key" ON "waitlist_entries"("email");
-- AddForeignKey
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "groups" ADD CONSTRAINT "groups_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "members" ADD CONSTRAINT "members_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "members" ADD CONSTRAINT "members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "students" ADD CONSTRAINT "students_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "payments" ADD CONSTRAINT "payments_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "payments" ADD CONSTRAINT "payments_studentId_fkey" FOREIGN KEY ("studentId") REFERENCES "students"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "waitlist_entries" ADD CONSTRAINT "waitlist_entries_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,117 @@
/*
Warnings:
- You are about to drop the column `groupId` on the `members` table. All the data in the column will be lost.
- You are about to drop the column `joinedAt` on the `members` table. All the data in the column will be lost.
- The `role` column on the `members` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- A unique constraint covering the columns `[organizationId,userId]` on the table `members` will be added. If there are existing duplicate values, this will fail.
- Added the required column `organizationId` to the `members` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "members" DROP CONSTRAINT "members_groupId_fkey";
-- DropIndex
DROP INDEX "members_groupId_userId_key";
-- AlterTable
ALTER TABLE "members" DROP COLUMN "groupId",
DROP COLUMN "joinedAt",
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "organizationId" TEXT NOT NULL,
DROP COLUMN "role",
ADD COLUMN "role" TEXT NOT NULL DEFAULT 'member';
-- AlterTable
ALTER TABLE "sessions" ADD COLUMN "activeOrganizationId" TEXT;
-- CreateTable
CREATE TABLE "organizations" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"logo" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "organizations_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "invitations" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"email" TEXT NOT NULL,
"role" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"inviterId" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "invitations_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "passkeys" (
"id" TEXT NOT NULL,
"name" TEXT,
"publicKey" TEXT NOT NULL,
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"deviceType" TEXT NOT NULL,
"backedUp" BOOLEAN NOT NULL,
"transports" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"aaguid" TEXT,
CONSTRAINT "passkeys_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "group_members" (
"id" TEXT NOT NULL,
"groupId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'MEMBER',
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "group_members_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "organizations_slug_key" ON "organizations"("slug");
-- CreateIndex
CREATE INDEX "invitations_organizationId_idx" ON "invitations"("organizationId");
-- CreateIndex
CREATE UNIQUE INDEX "passkeys_credentialID_key" ON "passkeys"("credentialID");
-- CreateIndex
CREATE INDEX "passkeys_userId_idx" ON "passkeys"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "group_members_groupId_userId_key" ON "group_members"("groupId", "userId");
-- CreateIndex
CREATE UNIQUE INDEX "members_organizationId_userId_key" ON "members"("organizationId", "userId");
-- AddForeignKey
ALTER TABLE "members" ADD CONSTRAINT "members_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "invitations" ADD CONSTRAINT "invitations_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "invitations" ADD CONSTRAINT "invitations_inviterId_fkey" FOREIGN KEY ("inviterId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "passkeys" ADD CONSTRAINT "passkeys_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "group_members" ADD CONSTRAINT "group_members_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "group_members" ADD CONSTRAINT "group_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,22 @@
/*
Warnings:
- You are about to drop the column `providerAccountId` on the `accounts` table. All the data in the column will be lost.
- A unique constraint covering the columns `[issuer,accountId]` on the table `accounts` will be added. If there are existing duplicate values, this will fail.
- Added the required column `accountId` to the `accounts` table without a default value. This is not possible if the table is not empty.
- Added the required column `issuer` to the `accounts` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "accounts_providerId_providerAccountId_key";
-- AlterTable
ALTER TABLE "accounts" DROP COLUMN "providerAccountId",
ADD COLUMN "accountId" TEXT NOT NULL,
ADD COLUMN "issuer" TEXT NOT NULL;
-- CreateIndex
CREATE INDEX "accounts_userId_idx" ON "accounts"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "accounts_issuer_accountId_key" ON "accounts"("issuer", "accountId");

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@@ -19,45 +19,51 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
groupsMember Member[]
groupsOwner Group[] @relation("OwnerGroups")
waitlist WaitlistEntry[]
accounts Account[]
sessions Session[]
passkeys Passkey[]
groupsMember GroupMember[]
groupsOwner Group[] @relation("OwnerGroups")
orgMemberships Member[]
orgInvitations Invitation[] @relation("InvitedBy")
waitlist WaitlistEntry[]
@@map("users")
}
model Account {
id String @id @default(cuid())
userId String
providerId String
providerAccountId String
refreshToken String?
accessToken String?
id String @id @default(cuid())
userId String
providerId String
accountId String
issuer String
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
idToken String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
scope String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, providerAccountId])
@@unique([issuer, accountId])
@@index([userId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
ipAddress String?
userAgent String?
activeOrganizationId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -76,6 +82,73 @@ model Verification {
@@map("verifications")
}
// Better Auth - Organization plugin
model Organization {
id String @id @default(cuid())
name String
slug String @unique
logo String?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members Member[]
invitations Invitation[]
@@map("organizations")
}
model Member {
id String @id @default(cuid())
organizationId String
userId String
role String @default("member")
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([organizationId, userId])
@@map("members")
}
model Invitation {
id String @id @default(cuid())
organizationId String
email String
role String
status String @default("pending")
inviterId String
expiresAt DateTime
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
inviter User @relation("InvitedBy", fields: [inviterId], references: [id], onDelete: Cascade)
@@index([organizationId])
@@map("invitations")
}
// Better Auth - Passkey plugin
model Passkey {
id String @id @default(cuid())
name String?
publicKey String
credentialID String @unique
userId String
counter Int
deviceType String
backedUp Boolean
transports String?
createdAt DateTime @default(now())
aaguid String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("passkeys")
}
// Domain models
model Group {
id String @id @default(cuid())
@@ -85,26 +158,26 @@ model Group {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner User @relation("OwnerGroups", fields: [createdById], references: [id], onDelete: Cascade)
members Member[]
owner User @relation("OwnerGroups", fields: [createdById], references: [id], onDelete: Cascade)
members GroupMember[]
students Student[]
payments Payment[]
@@map("groups")
}
model Member {
id String @id @default(cuid())
groupId String
userId String
role Role @default(MEMBER) // OWNER, ADMIN, MEMBER
model GroupMember {
id String @id @default(cuid())
groupId String
userId String
role Role @default(MEMBER) // OWNER, ADMIN, MEMBER
joinedAt DateTime @default(now())
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([groupId, userId])
@@map("members")
@@map("group_members")
}
enum Role {