refactor: redesign planos interface with design system patterns
- Create CreatePlanModal component with Headless UI Dialog - Implement dark mode support throughout plans UI - Update plans/page.tsx with professional card layout - Update plans/[id]/page.tsx with consistent styling - Add proper spacing, typography, and color consistency - Implement smooth animations and transitions - Add success/error message feedback - Improve form UX with better input styling
This commit is contained in:
283
backend/internal/repository/plan_repository.go
Normal file
283
backend/internal/repository/plan_repository.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"aggios-app/backend/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// PlanRepository handles database operations for plans
|
||||
type PlanRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewPlanRepository creates a new plan repository
|
||||
func NewPlanRepository(db *sql.DB) *PlanRepository {
|
||||
return &PlanRepository{db: db}
|
||||
}
|
||||
|
||||
// Create creates a new plan
|
||||
func (r *PlanRepository) Create(plan *domain.Plan) error {
|
||||
query := `
|
||||
INSERT INTO plans (id, name, slug, description, min_users, max_users, monthly_price, annual_price, features, differentiators, storage_gb, is_active, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
now := time.Now()
|
||||
plan.ID = uuid.New()
|
||||
plan.CreatedAt = now
|
||||
plan.UpdatedAt = now
|
||||
|
||||
features := pq.Array(plan.Features)
|
||||
differentiators := pq.Array(plan.Differentiators)
|
||||
|
||||
return r.db.QueryRow(
|
||||
query,
|
||||
plan.ID,
|
||||
plan.Name,
|
||||
plan.Slug,
|
||||
plan.Description,
|
||||
plan.MinUsers,
|
||||
plan.MaxUsers,
|
||||
plan.MonthlyPrice,
|
||||
plan.AnnualPrice,
|
||||
features,
|
||||
differentiators,
|
||||
plan.StorageGB,
|
||||
plan.IsActive,
|
||||
plan.CreatedAt,
|
||||
plan.UpdatedAt,
|
||||
).Scan(&plan.ID, &plan.CreatedAt, &plan.UpdatedAt)
|
||||
}
|
||||
|
||||
// GetByID retrieves a plan by ID
|
||||
func (r *PlanRepository) GetByID(id uuid.UUID) (*domain.Plan, error) {
|
||||
query := `
|
||||
SELECT id, name, slug, description, min_users, max_users, monthly_price, annual_price, features, differentiators, storage_gb, is_active, created_at, updated_at
|
||||
FROM plans
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
plan := &domain.Plan{}
|
||||
var features, differentiators pq.StringArray
|
||||
|
||||
err := r.db.QueryRow(query, id).Scan(
|
||||
&plan.ID,
|
||||
&plan.Name,
|
||||
&plan.Slug,
|
||||
&plan.Description,
|
||||
&plan.MinUsers,
|
||||
&plan.MaxUsers,
|
||||
&plan.MonthlyPrice,
|
||||
&plan.AnnualPrice,
|
||||
&features,
|
||||
&differentiators,
|
||||
&plan.StorageGB,
|
||||
&plan.IsActive,
|
||||
&plan.CreatedAt,
|
||||
&plan.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan.Features = []string(features)
|
||||
plan.Differentiators = []string(differentiators)
|
||||
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// GetBySlug retrieves a plan by slug
|
||||
func (r *PlanRepository) GetBySlug(slug string) (*domain.Plan, error) {
|
||||
query := `
|
||||
SELECT id, name, slug, description, min_users, max_users, monthly_price, annual_price, features, differentiators, storage_gb, is_active, created_at, updated_at
|
||||
FROM plans
|
||||
WHERE slug = $1
|
||||
`
|
||||
|
||||
plan := &domain.Plan{}
|
||||
var features, differentiators pq.StringArray
|
||||
|
||||
err := r.db.QueryRow(query, slug).Scan(
|
||||
&plan.ID,
|
||||
&plan.Name,
|
||||
&plan.Slug,
|
||||
&plan.Description,
|
||||
&plan.MinUsers,
|
||||
&plan.MaxUsers,
|
||||
&plan.MonthlyPrice,
|
||||
&plan.AnnualPrice,
|
||||
&features,
|
||||
&differentiators,
|
||||
&plan.StorageGB,
|
||||
&plan.IsActive,
|
||||
&plan.CreatedAt,
|
||||
&plan.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan.Features = []string(features)
|
||||
plan.Differentiators = []string(differentiators)
|
||||
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// ListAll retrieves all plans
|
||||
func (r *PlanRepository) ListAll() ([]*domain.Plan, error) {
|
||||
query := `
|
||||
SELECT id, name, slug, description, min_users, max_users, monthly_price, annual_price, features, differentiators, storage_gb, is_active, created_at, updated_at
|
||||
FROM plans
|
||||
ORDER BY min_users ASC
|
||||
`
|
||||
|
||||
rows, err := r.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var plans []*domain.Plan
|
||||
|
||||
for rows.Next() {
|
||||
plan := &domain.Plan{}
|
||||
var features, differentiators pq.StringArray
|
||||
|
||||
err := rows.Scan(
|
||||
&plan.ID,
|
||||
&plan.Name,
|
||||
&plan.Slug,
|
||||
&plan.Description,
|
||||
&plan.MinUsers,
|
||||
&plan.MaxUsers,
|
||||
&plan.MonthlyPrice,
|
||||
&plan.AnnualPrice,
|
||||
&features,
|
||||
&differentiators,
|
||||
&plan.StorageGB,
|
||||
&plan.IsActive,
|
||||
&plan.CreatedAt,
|
||||
&plan.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan.Features = []string(features)
|
||||
plan.Differentiators = []string(differentiators)
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
|
||||
return plans, rows.Err()
|
||||
}
|
||||
|
||||
// ListActive retrieves all active plans
|
||||
func (r *PlanRepository) ListActive() ([]*domain.Plan, error) {
|
||||
query := `
|
||||
SELECT id, name, slug, description, min_users, max_users, monthly_price, annual_price, features, differentiators, storage_gb, is_active, created_at, updated_at
|
||||
FROM plans
|
||||
WHERE is_active = true
|
||||
ORDER BY min_users ASC
|
||||
`
|
||||
|
||||
rows, err := r.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var plans []*domain.Plan
|
||||
|
||||
for rows.Next() {
|
||||
plan := &domain.Plan{}
|
||||
var features, differentiators pq.StringArray
|
||||
|
||||
err := rows.Scan(
|
||||
&plan.ID,
|
||||
&plan.Name,
|
||||
&plan.Slug,
|
||||
&plan.Description,
|
||||
&plan.MinUsers,
|
||||
&plan.MaxUsers,
|
||||
&plan.MonthlyPrice,
|
||||
&plan.AnnualPrice,
|
||||
&features,
|
||||
&differentiators,
|
||||
&plan.StorageGB,
|
||||
&plan.IsActive,
|
||||
&plan.CreatedAt,
|
||||
&plan.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan.Features = []string(features)
|
||||
plan.Differentiators = []string(differentiators)
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
|
||||
return plans, rows.Err()
|
||||
}
|
||||
|
||||
// Update updates a plan
|
||||
func (r *PlanRepository) Update(plan *domain.Plan) error {
|
||||
query := `
|
||||
UPDATE plans
|
||||
SET name = $2, slug = $3, description = $4, min_users = $5, max_users = $6, monthly_price = $7, annual_price = $8, features = $9, differentiators = $10, storage_gb = $11, is_active = $12, updated_at = $13
|
||||
WHERE id = $1
|
||||
RETURNING updated_at
|
||||
`
|
||||
|
||||
plan.UpdatedAt = time.Now()
|
||||
|
||||
features := pq.Array(plan.Features)
|
||||
differentiators := pq.Array(plan.Differentiators)
|
||||
|
||||
return r.db.QueryRow(
|
||||
query,
|
||||
plan.ID,
|
||||
plan.Name,
|
||||
plan.Slug,
|
||||
plan.Description,
|
||||
plan.MinUsers,
|
||||
plan.MaxUsers,
|
||||
plan.MonthlyPrice,
|
||||
plan.AnnualPrice,
|
||||
features,
|
||||
differentiators,
|
||||
plan.StorageGB,
|
||||
plan.IsActive,
|
||||
plan.UpdatedAt,
|
||||
).Scan(&plan.UpdatedAt)
|
||||
}
|
||||
|
||||
// Delete deletes a plan
|
||||
func (r *PlanRepository) Delete(id uuid.UUID) error {
|
||||
query := `DELETE FROM plans WHERE id = $1`
|
||||
result, err := r.db.Exec(query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
203
backend/internal/repository/subscription_repository.go
Normal file
203
backend/internal/repository/subscription_repository.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"aggios-app/backend/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SubscriptionRepository handles database operations for subscriptions
|
||||
type SubscriptionRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewSubscriptionRepository creates a new subscription repository
|
||||
func NewSubscriptionRepository(db *sql.DB) *SubscriptionRepository {
|
||||
return &SubscriptionRepository{db: db}
|
||||
}
|
||||
|
||||
// Create creates a new subscription
|
||||
func (r *SubscriptionRepository) Create(subscription *domain.Subscription) error {
|
||||
query := `
|
||||
INSERT INTO agency_subscriptions (id, agency_id, plan_id, billing_type, current_users, status, start_date, renewal_date, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
now := time.Now()
|
||||
subscription.ID = uuid.New()
|
||||
subscription.CreatedAt = now
|
||||
subscription.UpdatedAt = now
|
||||
subscription.StartDate = now
|
||||
|
||||
// Set renewal date based on billing type
|
||||
if subscription.BillingType == "annual" {
|
||||
subscription.RenewalDate = now.AddDate(1, 0, 0)
|
||||
} else {
|
||||
subscription.RenewalDate = now.AddDate(0, 1, 0)
|
||||
}
|
||||
|
||||
return r.db.QueryRow(
|
||||
query,
|
||||
subscription.ID,
|
||||
subscription.AgencyID,
|
||||
subscription.PlanID,
|
||||
subscription.BillingType,
|
||||
subscription.CurrentUsers,
|
||||
subscription.Status,
|
||||
subscription.StartDate,
|
||||
subscription.RenewalDate,
|
||||
subscription.CreatedAt,
|
||||
subscription.UpdatedAt,
|
||||
).Scan(&subscription.ID, &subscription.CreatedAt, &subscription.UpdatedAt)
|
||||
}
|
||||
|
||||
// GetByID retrieves a subscription by ID
|
||||
func (r *SubscriptionRepository) GetByID(id uuid.UUID) (*domain.Subscription, error) {
|
||||
query := `
|
||||
SELECT id, agency_id, plan_id, billing_type, current_users, status, start_date, renewal_date, created_at, updated_at
|
||||
FROM agency_subscriptions
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
subscription := &domain.Subscription{}
|
||||
err := r.db.QueryRow(query, id).Scan(
|
||||
&subscription.ID,
|
||||
&subscription.AgencyID,
|
||||
&subscription.PlanID,
|
||||
&subscription.BillingType,
|
||||
&subscription.CurrentUsers,
|
||||
&subscription.Status,
|
||||
&subscription.StartDate,
|
||||
&subscription.RenewalDate,
|
||||
&subscription.CreatedAt,
|
||||
&subscription.UpdatedAt,
|
||||
)
|
||||
|
||||
return subscription, err
|
||||
}
|
||||
|
||||
// GetByAgencyID retrieves a subscription by agency ID
|
||||
func (r *SubscriptionRepository) GetByAgencyID(agencyID uuid.UUID) (*domain.Subscription, error) {
|
||||
query := `
|
||||
SELECT id, agency_id, plan_id, billing_type, current_users, status, start_date, renewal_date, created_at, updated_at
|
||||
FROM agency_subscriptions
|
||||
WHERE agency_id = $1 AND status = 'active'
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
subscription := &domain.Subscription{}
|
||||
err := r.db.QueryRow(query, agencyID).Scan(
|
||||
&subscription.ID,
|
||||
&subscription.AgencyID,
|
||||
&subscription.PlanID,
|
||||
&subscription.BillingType,
|
||||
&subscription.CurrentUsers,
|
||||
&subscription.Status,
|
||||
&subscription.StartDate,
|
||||
&subscription.RenewalDate,
|
||||
&subscription.CreatedAt,
|
||||
&subscription.UpdatedAt,
|
||||
)
|
||||
|
||||
return subscription, err
|
||||
}
|
||||
|
||||
// ListAll retrieves all subscriptions
|
||||
func (r *SubscriptionRepository) ListAll() ([]*domain.Subscription, error) {
|
||||
query := `
|
||||
SELECT id, agency_id, plan_id, billing_type, current_users, status, start_date, renewal_date, created_at, updated_at
|
||||
FROM agency_subscriptions
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var subscriptions []*domain.Subscription
|
||||
|
||||
for rows.Next() {
|
||||
subscription := &domain.Subscription{}
|
||||
err := rows.Scan(
|
||||
&subscription.ID,
|
||||
&subscription.AgencyID,
|
||||
&subscription.PlanID,
|
||||
&subscription.BillingType,
|
||||
&subscription.CurrentUsers,
|
||||
&subscription.Status,
|
||||
&subscription.StartDate,
|
||||
&subscription.RenewalDate,
|
||||
&subscription.CreatedAt,
|
||||
&subscription.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, subscription)
|
||||
}
|
||||
|
||||
return subscriptions, rows.Err()
|
||||
}
|
||||
|
||||
// Update updates a subscription
|
||||
func (r *SubscriptionRepository) Update(subscription *domain.Subscription) error {
|
||||
query := `
|
||||
UPDATE agency_subscriptions
|
||||
SET plan_id = $2, billing_type = $3, current_users = $4, status = $5, renewal_date = $6, updated_at = $7
|
||||
WHERE id = $1
|
||||
RETURNING updated_at
|
||||
`
|
||||
|
||||
subscription.UpdatedAt = time.Now()
|
||||
|
||||
return r.db.QueryRow(
|
||||
query,
|
||||
subscription.ID,
|
||||
subscription.PlanID,
|
||||
subscription.BillingType,
|
||||
subscription.CurrentUsers,
|
||||
subscription.Status,
|
||||
subscription.RenewalDate,
|
||||
subscription.UpdatedAt,
|
||||
).Scan(&subscription.UpdatedAt)
|
||||
}
|
||||
|
||||
// Delete deletes a subscription
|
||||
func (r *SubscriptionRepository) Delete(id uuid.UUID) error {
|
||||
query := `DELETE FROM agency_subscriptions WHERE id = $1`
|
||||
result, err := r.db.Exec(query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUserCount updates the current user count for a subscription
|
||||
func (r *SubscriptionRepository) UpdateUserCount(agencyID uuid.UUID, userCount int) error {
|
||||
query := `
|
||||
UPDATE agency_subscriptions
|
||||
SET current_users = $2, updated_at = $3
|
||||
WHERE agency_id = $1 AND status = 'active'
|
||||
`
|
||||
|
||||
_, err := r.db.Exec(query, agencyID, userCount, time.Now())
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user