invite and saving fitness profile
added, need polishing
This commit is contained in:
1 parent
daaf5aa2dd
commit
118efad70f
10 files changed
+202
-72
No files matched your search
@@ -16,10 +16,20 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const profile = db
|
||||
.prepare(
|
||||
`SELECT * FROM fitness_profiles WHERE user_id = ?`
|
||||
`SELECT * FROM fitness_profiles WHERE userId = ?`
|
||||
)
|
||||
.get(userId);
|
||||
|
||||
if (profile) {
|
||||
const p = profile as any;
|
||||
// Parse JSON fields
|
||||
try {
|
||||
p.fitnessGoals = JSON.parse(p.fitnessGoals || '[]');
|
||||
} catch (e) {
|
||||
p.fitnessGoals = [];
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ profile: profile || null });
|
||||
} catch (error) {
|
||||
console.error("Error fetching fitness profile:", error);
|
||||
@@ -45,19 +55,22 @@ export async function POST(request: NextRequest) {
|
||||
weight,
|
||||
age,
|
||||
gender,
|
||||
fitnessGoal,
|
||||
fitnessGoals, // Changed from fitnessGoal
|
||||
activityLevel,
|
||||
medicalConditions,
|
||||
allergies,
|
||||
injuries,
|
||||
exerciseHabits,
|
||||
dietHabits
|
||||
} = body;
|
||||
|
||||
// Check if profile exists
|
||||
const existingProfile = db
|
||||
.prepare(`SELECT id FROM fitness_profiles WHERE user_id = ?`)
|
||||
.get(userId) as { id: string } | undefined;
|
||||
.prepare(`SELECT userId FROM fitness_profiles WHERE userId = ?`)
|
||||
.get(userId) as { userId: string } | undefined;
|
||||
|
||||
const now = Date.now();
|
||||
const now = new Date().toISOString();
|
||||
const fitnessGoalsJson = JSON.stringify(fitnessGoals || []);
|
||||
|
||||
if (existingProfile) {
|
||||
// Update existing profile
|
||||
@@ -67,52 +80,55 @@ export async function POST(request: NextRequest) {
|
||||
weight = ?,
|
||||
age = ?,
|
||||
gender = ?,
|
||||
fitness_goal = ?,
|
||||
activity_level = ?,
|
||||
medical_conditions = ?,
|
||||
fitnessGoals = ?,
|
||||
activityLevel = ?,
|
||||
medicalConditions = ?,
|
||||
allergies = ?,
|
||||
injuries = ?,
|
||||
updated_at = ?
|
||||
WHERE user_id = ?`
|
||||
exerciseHabits = ?,
|
||||
dietHabits = ?,
|
||||
updatedAt = ?
|
||||
WHERE userId = ?`
|
||||
).run(
|
||||
height || null,
|
||||
weight || null,
|
||||
age || null,
|
||||
gender || null,
|
||||
fitnessGoal || null,
|
||||
fitnessGoalsJson,
|
||||
activityLevel || null,
|
||||
medicalConditions || null,
|
||||
allergies || null,
|
||||
injuries || null,
|
||||
exerciseHabits || null,
|
||||
dietHabits || null,
|
||||
now,
|
||||
userId
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
message: "Fitness profile updated successfully",
|
||||
profileId: existingProfile.id,
|
||||
userId: userId,
|
||||
});
|
||||
} else {
|
||||
// Create new profile
|
||||
const profileId = `fp_${randomBytes(16).toString("hex")}`;
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO fitness_profiles
|
||||
(id, user_id, height, weight, age, gender, fitness_goal, activity_level,
|
||||
medical_conditions, allergies, injuries, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
(userId, height, weight, age, gender, fitnessGoals, activityLevel,
|
||||
medicalConditions, allergies, injuries, exerciseHabits, dietHabits, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
profileId,
|
||||
userId,
|
||||
height || null,
|
||||
weight || null,
|
||||
age || null,
|
||||
gender || null,
|
||||
fitnessGoal || null,
|
||||
fitnessGoalsJson,
|
||||
activityLevel || null,
|
||||
medicalConditions || null,
|
||||
allergies || null,
|
||||
injuries || null,
|
||||
exerciseHabits || null,
|
||||
dietHabits || null,
|
||||
now,
|
||||
now
|
||||
);
|
||||
@@ -120,7 +136,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "Fitness profile created successfully",
|
||||
profileId,
|
||||
userId,
|
||||
},
|
||||
{ status: 201 }
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getDatabase } from "../../../lib/database/index";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
import { auth, clerkClient } from "@clerk/nextjs/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
@@ -64,9 +64,9 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { email, password, firstName, lastName, role, phone } = body;
|
||||
const { email, firstName, lastName, role, phone } = body;
|
||||
|
||||
if (!email || !password || !firstName || !lastName || !role) {
|
||||
if (!email || !firstName || !lastName || !role) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing required fields" },
|
||||
{ status: 400 },
|
||||
@@ -75,7 +75,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Enforce Hierarchy
|
||||
const allowed = {
|
||||
superAdmin: ["admin", "trainer", "client"], // Super Admin can create anyone (except maybe another superAdmin via this UI?)
|
||||
superAdmin: ["admin", "trainer", "client"],
|
||||
admin: ["trainer", "client"],
|
||||
trainer: ["client"],
|
||||
client: []
|
||||
@@ -89,7 +89,7 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
// Check if user already exists locally
|
||||
const existingUser = await db.getUserByEmail(email);
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
@@ -98,13 +98,38 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
// Create Clerk Invitation
|
||||
// Note: We pass the role in publicMetadata so it persists when they sign up
|
||||
try {
|
||||
const client = await clerkClient();
|
||||
await client.invitations.createInvitation({
|
||||
emailAddress: email,
|
||||
publicMetadata: {
|
||||
role,
|
||||
},
|
||||
ignoreExisting: true // Don't fail if invite exists
|
||||
});
|
||||
} catch (clerkError: any) {
|
||||
console.error("Clerk invitation error:", clerkError);
|
||||
// If user already exists in Clerk, we might want to handle it.
|
||||
// But for now, let's proceed to create local record if invite sent or if they exist.
|
||||
if (clerkError.errors?.[0]?.code === 'form_identifier_exists') {
|
||||
return NextResponse.json(
|
||||
{ error: "User already exists in Clerk system" },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to send invitation: " + (clerkError.message || "Unknown error") },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
// Create user
|
||||
// Create user in local DB with temporary ID (will be migrated on first login)
|
||||
// We set a placeholder password since it's required by schema but won't be used
|
||||
const newUserId = await db.createUser({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
password: "INVITED_USER_PENDING",
|
||||
firstName,
|
||||
lastName,
|
||||
role,
|
||||
@@ -121,7 +146,7 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ userId: newUserId.id }, { status: 201 });
|
||||
return NextResponse.json({ userId: newUserId.id, message: "Invitation sent" }, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("Create user error:", error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -139,23 +139,44 @@ export function UserManagement() {
|
||||
};
|
||||
|
||||
const handleSaveEdit = async () => {
|
||||
if (!editForm || !selectedUser) return;
|
||||
if (!editForm) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/users", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: selectedUser.id, ...editForm }),
|
||||
});
|
||||
if (response.ok) {
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
fetchUsers();
|
||||
if (selectedUser) {
|
||||
// Update existing user
|
||||
const response = await fetch("/api/users", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: selectedUser.id, ...editForm }),
|
||||
});
|
||||
if (response.ok) {
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
fetchUsers();
|
||||
} else {
|
||||
alert("Error updating user");
|
||||
}
|
||||
} else {
|
||||
alert("Error updating user");
|
||||
// Create (Invite) new user
|
||||
const response = await fetch("/api/users", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(editForm),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
fetchUsers();
|
||||
alert("Invitation sent successfully!");
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Error sending invitation: ${errorData.error}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("An unexpected error occurred");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -196,6 +217,22 @@ export function UserManagement() {
|
||||
>
|
||||
Edit User
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setEditForm({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
role: "client",
|
||||
phone: "",
|
||||
});
|
||||
setSelectedUser(null);
|
||||
setIsEditing(true);
|
||||
}}
|
||||
>
|
||||
Invite User
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleDeleteUser}
|
||||
@@ -265,7 +302,7 @@ export function UserManagement() {
|
||||
{isEditing && editForm && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg shadow-lg max-w-md w-full">
|
||||
<h3 className="text-lg font-semibold mb-4">Edit User</h3>
|
||||
<h3 className="text-lg font-semibold mb-4">{selectedUser ? 'Edit User' : 'Invite New User'}</h3>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -310,6 +347,7 @@ export function UserManagement() {
|
||||
}
|
||||
className="w-full border border-gray-300 rounded px-3 py-2"
|
||||
required
|
||||
disabled={!!selectedUser} // Disable email edit for existing users if desired, or keep enabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
@@ -360,7 +398,7 @@ export function UserManagement() {
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
Save
|
||||
{selectedUser ? 'Save Changes' : 'Send Invitation'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -91,6 +91,8 @@ export class SQLiteDatabase implements IDatabase {
|
||||
exerciseHabits TEXT,
|
||||
dietHabits TEXT,
|
||||
medicalConditions TEXT,
|
||||
allergies TEXT,
|
||||
injuries TEXT,
|
||||
createdAt DATETIME NOT NULL,
|
||||
updatedAt DATETIME NOT NULL,
|
||||
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
|
||||
@@ -198,6 +200,7 @@ export class SQLiteDatabase implements IDatabase {
|
||||
async deleteUser(id: string): Promise<boolean> {
|
||||
if (!this.db) throw new Error('Database not connected')
|
||||
|
||||
const stmt = this.db.prepare('DELETE FROM users WHERE id = ?')
|
||||
const result = stmt.run(id)
|
||||
return (result.changes || 0) > 0
|
||||
}
|
||||
@@ -298,15 +301,15 @@ export class SQLiteDatabase implements IDatabase {
|
||||
const stmt = this.db.prepare(
|
||||
`INSERT INTO fitness_profiles
|
||||
(userId, height, weight, age, gender, activityLevel, fitnessGoals,
|
||||
exerciseHabits, dietHabits, medicalConditions, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
exerciseHabits, dietHabits, medicalConditions, allergies, injuries, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
|
||||
stmt.run(
|
||||
profile.userId, profile.height, profile.weight, profile.age, profile.gender,
|
||||
profile.activityLevel, JSON.stringify(profile.fitnessGoals), profile.exerciseHabits,
|
||||
profile.dietHabits, profile.medicalConditions, profile.createdAt.toISOString(),
|
||||
profile.updatedAt.toISOString()
|
||||
profile.dietHabits, profile.medicalConditions, profile.allergies, profile.injuries,
|
||||
profile.createdAt.toISOString(), profile.updatedAt.toISOString()
|
||||
)
|
||||
|
||||
return profile
|
||||
@@ -468,6 +471,8 @@ export class SQLiteDatabase implements IDatabase {
|
||||
exerciseHabits: row.exerciseHabits,
|
||||
dietHabits: row.dietHabits,
|
||||
medicalConditions: row.medicalConditions,
|
||||
allergies: row.allergies,
|
||||
injuries: row.injuries,
|
||||
createdAt: new Date(row.createdAt),
|
||||
updatedAt: new Date(row.updatedAt)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface FitnessProfile {
|
||||
exerciseHabits: string;
|
||||
dietHabits: string;
|
||||
medicalConditions: string;
|
||||
allergies?: string;
|
||||
injuries?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user