Compare commits

..

2 Commits

Author SHA1 Message Date
James Pattinson
731c615d07 small tweaks 2025-11-10 17:37:08 +00:00
James Pattinson
a41e5a8cbb DB schema init.sql 2025-11-10 17:12:02 +00:00
7 changed files with 271 additions and 32 deletions

View File

@@ -28,6 +28,15 @@ async def update_current_user_profile(
"""Update current user's profile"""
update_data = user_update.model_dump(exclude_unset=True)
# Check email uniqueness if email is being updated
if 'email' in update_data and update_data['email'] != current_user.email:
existing_user = db.query(User).filter(User.email == update_data['email']).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
for field, value in update_data.items():
setattr(current_user, field, value)

View File

@@ -2,18 +2,6 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .core.config import settings
from .api.v1 import api_router
from .core.database import engine, Base, SessionLocal
from .core.init_db import init_default_data
# Create database tables
Base.metadata.create_all(bind=engine)
# Initialize default data
db = SessionLocal()
try:
init_default_data(db)
finally:
db.close()
app = FastAPI(
title=settings.APP_NAME,

View File

@@ -59,7 +59,7 @@ class User(Base):
last_name = Column(String(100), nullable=False)
phone = Column(String(20), nullable=True)
address = Column(Text, nullable=True)
role = Column(SQLEnum(UserRole), default=UserRole.MEMBER, nullable=False)
role = Column(SQLEnum(UserRole, values_callable=lambda x: [e.value for e in x]), default=UserRole.MEMBER, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
@@ -95,7 +95,7 @@ class Membership(Base):
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
tier_id = Column(Integer, ForeignKey("membership_tiers.id"), nullable=False)
status = Column(SQLEnum(MembershipStatus), default=MembershipStatus.PENDING, nullable=False)
status = Column(SQLEnum(MembershipStatus, values_callable=lambda x: [e.value for e in x]), default=MembershipStatus.PENDING, nullable=False)
start_date = Column(Date, nullable=False)
end_date = Column(Date, nullable=False)
auto_renew = Column(Boolean, default=False, nullable=False)
@@ -115,8 +115,8 @@ class Payment(Base):
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
membership_id = Column(Integer, ForeignKey("memberships.id"), nullable=True)
amount = Column(Float, nullable=False)
payment_method = Column(SQLEnum(PaymentMethod), nullable=False)
status = Column(SQLEnum(PaymentStatus), default=PaymentStatus.PENDING, nullable=False)
payment_method = Column(SQLEnum(PaymentMethod, values_callable=lambda x: [e.value for e in x]), nullable=False)
status = Column(SQLEnum(PaymentStatus, values_callable=lambda x: [e.value for e in x]), default=PaymentStatus.PENDING, nullable=False)
transaction_id = Column(String(255), nullable=True)
payment_date = Column(DateTime, nullable=True)
notes = Column(Text, nullable=True)
@@ -137,7 +137,7 @@ class Event(Base):
event_date = Column(DateTime, nullable=False)
location = Column(String(255), nullable=True)
max_attendees = Column(Integer, nullable=True)
status = Column(SQLEnum(EventStatus), default=EventStatus.DRAFT, nullable=False)
status = Column(SQLEnum(EventStatus, values_callable=lambda x: [e.value for e in x]), default=EventStatus.DRAFT, nullable=False)
created_by = Column(Integer, ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
@@ -152,7 +152,7 @@ class EventRSVP(Base):
id = Column(Integer, primary_key=True, index=True)
event_id = Column(Integer, ForeignKey("events.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
status = Column(SQLEnum(RSVPStatus), default=RSVPStatus.PENDING, nullable=False)
status = Column(SQLEnum(RSVPStatus, values_callable=lambda x: [e.value for e in x]), default=RSVPStatus.PENDING, nullable=False)
attended = Column(Boolean, default=False, nullable=False)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
@@ -302,7 +302,7 @@ class EmailBounce(Base):
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255), nullable=False, index=True)
bounce_type = Column(SQLEnum(BounceType), nullable=False)
bounce_type = Column(SQLEnum(BounceType, values_callable=lambda x: [e.value for e in x]), nullable=False)
bounce_reason = Column(String(500), nullable=True)
smtp2go_message_id = Column(String(255), nullable=True, index=True)
bounce_date = Column(DateTime, nullable=False)

View File

@@ -18,6 +18,7 @@ class UserCreate(UserBase):
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
first_name: Optional[str] = Field(None, min_length=1, max_length=100)
last_name: Optional[str] = Field(None, min_length=1, max_length=100)
phone: Optional[str] = None

View File

@@ -1,19 +1,233 @@
-- Initialize database with default membership tiers
-- Initialize database with complete schema and default data
-- Set character set to UTF-8 to prevent encoding issues
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- Create all tables
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
hashed_password VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
address TEXT,
role ENUM('member', 'admin', 'super_admin') NOT NULL DEFAULT 'member',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL,
INDEX idx_email (email)
);
CREATE TABLE membership_tiers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
annual_fee DECIMAL(10,2) NOT NULL,
benefits TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE memberships (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
tier_id INT NOT NULL,
status ENUM('active', 'expired', 'pending', 'cancelled') NOT NULL DEFAULT 'pending',
start_date DATE NOT NULL,
end_date DATE NOT NULL,
auto_renew BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (tier_id) REFERENCES membership_tiers(id),
INDEX idx_user_id (user_id),
INDEX idx_tier_id (tier_id)
);
CREATE TABLE payments (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
membership_id INT,
amount DECIMAL(10,2) NOT NULL,
payment_method ENUM('square', 'cash', 'check', 'dummy') NOT NULL,
status ENUM('pending', 'completed', 'failed', 'refunded') NOT NULL DEFAULT 'pending',
transaction_id VARCHAR(255),
payment_date TIMESTAMP NULL,
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (membership_id) REFERENCES memberships(id),
INDEX idx_user_id (user_id),
INDEX idx_membership_id (membership_id)
);
CREATE TABLE events (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
event_date TIMESTAMP NOT NULL,
location VARCHAR(255),
max_attendees INT,
status ENUM('draft', 'published', 'cancelled', 'completed') NOT NULL DEFAULT 'draft',
created_by INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id),
INDEX idx_created_by (created_by),
INDEX idx_event_date (event_date)
);
CREATE TABLE event_rsvps (
id INT AUTO_INCREMENT PRIMARY KEY,
event_id INT NOT NULL,
user_id INT NOT NULL,
status ENUM('pending', 'attending', 'not_attending', 'maybe') NOT NULL DEFAULT 'pending',
attended BOOLEAN NOT NULL DEFAULT FALSE,
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY unique_event_user (event_id, user_id),
INDEX idx_event_id (event_id),
INDEX idx_user_id (user_id)
);
CREATE TABLE volunteer_roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE volunteer_assignments (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
role_id INT NOT NULL,
assigned_date DATE NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES volunteer_roles(id),
INDEX idx_user_id (user_id),
INDEX idx_role_id (role_id)
);
CREATE TABLE volunteer_schedules (
id INT AUTO_INCREMENT PRIMARY KEY,
assignment_id INT NOT NULL,
schedule_date DATE NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
location VARCHAR(255),
notes TEXT,
completed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (assignment_id) REFERENCES volunteer_assignments(id) ON DELETE CASCADE,
INDEX idx_assignment_id (assignment_id),
INDEX idx_schedule_date (schedule_date)
);
CREATE TABLE certificates (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
certificate_name VARCHAR(255) NOT NULL,
issuing_organization VARCHAR(255),
issue_date DATE NOT NULL,
expiry_date DATE,
certificate_number VARCHAR(100),
file_path VARCHAR(500),
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user_id (user_id)
);
CREATE TABLE files (
id INT AUTO_INCREMENT PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INT NOT NULL,
mime_type VARCHAR(100) NOT NULL,
min_tier_id INT,
description TEXT,
uploaded_by INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (min_tier_id) REFERENCES membership_tiers(id),
FOREIGN KEY (uploaded_by) REFERENCES users(id),
INDEX idx_min_tier_id (min_tier_id),
INDEX idx_uploaded_by (uploaded_by)
);
CREATE TABLE notifications (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
subject VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
email_sent BOOLEAN NOT NULL DEFAULT FALSE,
sent_at TIMESTAMP NULL,
error_message TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user_id (user_id)
);
CREATE TABLE password_reset_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
token VARCHAR(255) UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
used BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_token (token),
INDEX idx_user_id (user_id)
);
CREATE TABLE email_templates (
id INT AUTO_INCREMENT PRIMARY KEY,
template_key VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
subject VARCHAR(255) NOT NULL,
html_body TEXT NOT NULL,
text_body TEXT,
variables TEXT, -- JSON string of available variables
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_template_key (template_key)
);
-- Create email bounces table
CREATE TABLE email_bounces (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
bounce_type ENUM('hard', 'soft') NOT NULL,
bounce_type ENUM('hard', 'soft', 'complaint', 'unsubscribe') NOT NULL,
bounce_reason VARCHAR(500),
smtp2go_message_id VARCHAR(255),
bounced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
resolved_at TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
bounce_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_smtp2go_message_id (smtp2go_message_id),
INDEX idx_active (is_active)
);
-- Seed initial data
-- Create default membership tiers
INSERT INTO membership_tiers (name, description, annual_fee, benefits, is_active, created_at, updated_at)
VALUES
@@ -22,7 +236,34 @@ VALUES
('Corporate', 'Corporate membership for businesses', 100.00, 'All benefits plus corporate recognition, promotional opportunities, file access', TRUE, NOW(), NOW());
-- Create default admin user (password: admin123)
-- Note: In production, this should be changed immediately
INSERT INTO users (email, hashed_password, first_name, last_name, role, is_active, created_at, updated_at)
VALUES
('admin@swanseaairport.org', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5aeWG/7gYjV8W', 'System', 'Administrator', 'super_admin', TRUE, NOW(), NOW());
('admin@swanseaairport.org', '$2b$12$eeuS7kW4xUYZPLx4LgBGaeoGhPu/cg/9M3WEanWToTwtLOLppJmzq', 'System', 'Administrator', 'super_admin', TRUE, NOW(), NOW());
-- Create default email templates
INSERT INTO email_templates (template_key, name, subject, html_body, text_body, variables, is_active, created_at, updated_at)
VALUES
('welcome', 'Welcome Email', 'Welcome to Swansea Airport Stakeholders Alliance - Your Account is Ready',
'<html><body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;"><h2 style="color: #0066cc;">Welcome to Swansea Airport Stakeholders Alliance!</h2><p>Hello {first_name},</p><p>Thank you for registering with us. Your account has been successfully created.</p><p>You can now:</p><ul><li>Browse membership tiers and select one that suits you</li><li>View upcoming events and meetings</li><li>Access your membership portal</li></ul><p>If you have any questions, please don\'t hesitate to contact us.</p><p>Best regards,<br><strong>Swansea Airport Stakeholders Alliance</strong></p></body></html>',
'Welcome to Swansea Airport Stakeholders Alliance!\n\nHello {first_name},\n\nThank you for registering with us. Your account has been successfully created.\n\nYou can now:\n- Browse membership tiers and select one that suits you\n- View upcoming events and meetings\n- Access your membership portal\n\nIf you have any questions, please don\'t hesitate to contact us.\n\nBest regards,\nSwansea Airport Stakeholders Alliance',
'["first_name"]', TRUE, NOW(), NOW()),
('payment_confirmation', 'Payment Confirmation', 'Payment Confirmation - Swansea Airport Stakeholders Alliance',
'<html><body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;"><h2 style="color: #28a745;">Payment Confirmed!</h2><p>Hello {first_name},</p><p>We have received your payment. Thank you!</p><div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;"><p style="margin: 5px 0;"><strong>Amount:</strong> {amount}</p><p style="margin: 5px 0;"><strong>Payment Method:</strong> {payment_method}</p><p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p></div><p>Your membership is now active. You can access all the benefits associated with your tier.</p><p>Best regards,<br><strong>Swansea Airport Stakeholders Alliance</strong></p></body></html>',
'Payment Confirmed!\n\nHello {first_name},\n\nWe have received your payment. Thank you!\n\nAmount: {amount}\nPayment Method: {payment_method}\nMembership Tier: {membership_tier}\n\nYour membership is now active. You can access all the benefits associated with your tier.\n\nBest regards,\nSwansea Airport Stakeholders Alliance',
'["first_name", "amount", "payment_method", "membership_tier"]', TRUE, NOW(), NOW()),
('membership_activation', 'Membership Activation', 'Your Swansea Airport Stakeholders Alliance Membership is Now Active!',
'<html><body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;"><h2 style="color: #28a745;">Welcome to Swansea Airport Stakeholders Alliance!</h2><p>Hello {first_name},</p><p>Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.</p><div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #28a745;"><h3 style="margin-top: 0; color: #28a745;">Membership Details</h3><p style="margin: 8px 0;"><strong>Membership Tier:</strong> {membership_tier}</p><p style="margin: 8px 0;"><strong>Annual Fee:</strong> {annual_fee}</p><p style="margin: 8px 0;"><strong>Next Renewal Date:</strong> {renewal_date}</p></div><div style="background-color: #e9ecef; padding: 20px; border-radius: 8px; margin: 20px 0;"><h3 style="margin-top: 0; color: #495057;">Payment Information</h3><p style="margin: 8px 0;"><strong>Amount Paid:</strong> {payment_amount}</p><p style="margin: 8px 0;"><strong>Payment Method:</strong> {payment_method}</p><p style="margin: 8px 0;"><strong>Payment Date:</strong> {payment_date}</p></div><p>Your membership will automatically renew on <strong>{renewal_date}</strong> unless you choose to cancel it. You can manage your membership settings in your account dashboard.</p><p>If you have any questions about your membership or need assistance, please don\'t hesitate to contact us.</p><p>Welcome to the Swansea Airport Stakeholders Alliance community!</p><p>Best regards,<br><strong>Swansea Airport Stakeholders Alliance Team</strong></p></body></html>',
'Welcome to Swansea Airport Stakeholders Alliance!\n\nHello {first_name},\n\nGreat news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.\n\nMEMBERSHIP DETAILS\n------------------\nMembership Tier: {membership_tier}\nAnnual Fee: {annual_fee}\nNext Renewal Date: {renewal_date}\n\nPAYMENT INFORMATION\n-------------------\nAmount Paid: {payment_amount}\nPayment Method: {payment_method}\nPayment Date: {payment_date}\n\nYour membership will automatically renew on {renewal_date} unless you choose to cancel it. You can manage your membership settings in your account dashboard.\n\nIf you have any questions about your membership or need assistance, please don\'t hesitate to contact us.\n\nWelcome to the Swansea Airport Stakeholders Alliance community!\n\nBest regards,\nSwansea Airport Stakeholders Alliance Team',
'["first_name", "membership_tier", "annual_fee", "payment_amount", "payment_method", "renewal_date", "payment_date"]', TRUE, NOW(), NOW()),
('renewal_reminder', 'Renewal Reminder', 'Membership Renewal Reminder - Swansea Airport Stakeholders Alliance',
'<html><body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;"><h2 style="color: #0066cc;">Membership Renewal Reminder</h2><p>Hello {first_name},</p><p>This is a friendly reminder that your <strong>{membership_tier}</strong> membership will expire on <strong>{expiry_date}</strong>.</p><p>To continue enjoying your membership benefits, please renew your membership.</p><div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;"><p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p><p style="margin: 5px 0;"><strong>Annual Fee:</strong> {annual_fee}</p><p style="margin: 5px 0;"><strong>Expires:</strong> {expiry_date}</p></div><p>Please log in to your account to renew your membership.</p><p>Best regards,<br><strong>Swansea Airport Stakeholders Alliance</strong></p></body></html>',
'Membership Renewal Reminder\n\nHello {first_name},\n\nThis is a friendly reminder that your {membership_tier} membership will expire on {expiry_date}.\n\nTo continue enjoying your membership benefits, please renew your membership.\n\nMembership Tier: {membership_tier}\nAnnual Fee: {annual_fee}\nExpires: {expiry_date}\n\nPlease log in to your account to renew your membership.\n\nBest regards,\nSwansea Airport Stakeholders Alliance',
'["first_name", "expiry_date", "membership_tier", "annual_fee"]', TRUE, NOW(), NOW()),
('password_reset', 'Password Reset', 'Password Reset Request - Swansea Airport Stakeholders Alliance Member Portal',
'<html><body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;"><h2 style="color: #0066cc;">Password Reset Request</h2><p>Hello {first_name},</p><p>You have requested to reset your password for your Swansea Airport Stakeholders Alliance member account.</p><p>Please click the button below to reset your password:</p><div style="text-align: center; margin: 30px 0;"><a href="{reset_url}" style="background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a></div><p>If the button doesn\'t work, you can copy and paste this link into your browser:</p><p style="word-break: break-all; background-color: #f5f5f5; padding: 10px; border-radius: 3px;">{reset_url}</p><p><strong>This link will expire in 1 hour.</strong></p><p>If you didn\'t request this password reset, please ignore this email. Your password will remain unchanged.</p><p>For security reasons, please don\'t share this email with anyone.</p><p>Best regards,<br><strong>Swansea Airport Stakeholders Alliance</strong></p></body></html>',
'Password Reset Request\n\nHello {first_name},\n\nYou have requested to reset your password for your Swansea Airport Stakeholders Alliance member account.\n\nPlease use this link to reset your password: {reset_url}\n\nThis link will expire in 1 hour.\n\nIf you didn\'t request this password reset, please ignore this email. Your password will remain unchanged.\n\nFor security reasons, please don\'t share this email with anyone.\n\nBest regards,\nSwansea Airport Stakeholders Alliance',
'["first_name", "reset_url"]', TRUE, NOW(), NOW());

View File

@@ -147,7 +147,7 @@ const MembershipSetup: React.FC<MembershipSetupProps> = ({ onMembershipCreated,
<div style={{ backgroundColor: '#fff3cd', border: '1px solid #ffeaa7', borderRadius: '4px', padding: '16px', marginBottom: '20px' }}>
<strong>Demo Payment</strong>
<p style={{ marginTop: '8px', marginBottom: 0 }}>
This is a fake payment flow for demo purposes. Square will come soon
This is a Cash payment flow for demo purposes. Square / Paypal etc will come soon
</p>
</div>
@@ -159,7 +159,7 @@ const MembershipSetup: React.FC<MembershipSetupProps> = ({ onMembershipCreated,
disabled={loading}
style={{ marginRight: '10px' }}
>
{loading ? 'Processing...' : 'Complete Fake Payment'}
{loading ? 'Processing...' : 'Complete Cash Payment'}
</button>
<button
type="button"

View File

@@ -46,7 +46,7 @@ const Login: React.FC = () => {
}}>
<h1 style={{ color: '#333', marginBottom: '16px', fontSize: '2.2rem' }}>Welcome to SASA</h1>
<p style={{ fontSize: '1.1rem', color: '#666', lineHeight: '1.6', marginBottom: '20px' }}>
REPLACE WITH BOB WORDS: Swansea Airport Supporters Association is a community interest company run by volunteers, which holds the lease of Swansea Airport.
REPLACE WITH BOB WORDS: Swansea Airport Stakeholder's Association (SASA) is a community interest company run by volunteers, which holds the lease of Swansea Airport.
</p>
<p style={{ fontSize: '1rem', color: '#555', lineHeight: '1.5' }}>
Join our community of aviation enthusiasts and support the future of Swansea Airport.