invite and saving fitness profile

added, need polishing
This commit is contained in:
echo 2025-11-20 18:36:40 +01:00
parent daaf5aa2dd
commit 118efad70f
10 changed files with 202 additions and 72 deletions

Binary file not shown.

View File

@ -22,6 +22,7 @@
"autoprefixer": "^10.4.16",
"axios": "^1.13.2",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@ -112,6 +113,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -2396,7 +2398,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@ -2417,7 +2418,6 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@ -2427,8 +2427,7 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@testing-library/jest-dom": {
"version": "6.9.1",
@ -2504,8 +2503,7 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@ -2689,6 +2687,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@ -2699,6 +2698,7 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@ -2789,6 +2789,7 @@
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3",
@ -3333,6 +3334,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3988,6 +3990,17 @@
"bcrypt": "bin/bcrypt"
}
},
"node_modules/better-sqlite3": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -4062,6 +4075,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@ -5296,6 +5310,7 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5481,6 +5496,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -8324,18 +8340,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@ -8563,7 +8567,6 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@ -9076,6 +9079,7 @@
"resolved": "https://registry.npmjs.org/next/-/next-16.0.1.tgz",
"integrity": "sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "16.0.1",
"@swc/helpers": "0.5.15",
@ -9744,6 +9748,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@ -9881,7 +9886,6 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@ -9897,7 +9901,6 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@ -9910,8 +9913,7 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/promise-inflight": {
"version": "1.0.1",
@ -10043,6 +10045,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -10052,6 +10055,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -10064,6 +10068,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz",
"integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@ -10087,6 +10092,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@ -10185,7 +10191,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@ -11638,6 +11645,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -11857,6 +11865,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -12443,6 +12452,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@ -25,6 +25,7 @@
"autoprefixer": "^10.4.16",
"axios": "^1.13.2",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",

View File

@ -0,0 +1,33 @@
const Database = require('better-sqlite3');
const path = require('path');
const dbPath = path.join(__dirname, '../../../data/fitai.db');
const db = new Database(dbPath);
console.log('Migrating fitness_profiles table...');
try {
// Check if columns exist
const tableInfo = db.prepare('PRAGMA table_info(fitness_profiles)').all();
const columns = tableInfo.map(c => c.name);
if (!columns.includes('allergies')) {
console.log('Adding allergies column...');
db.prepare('ALTER TABLE fitness_profiles ADD COLUMN allergies TEXT').run();
} else {
console.log('allergies column already exists.');
}
if (!columns.includes('injuries')) {
console.log('Adding injuries column...');
db.prepare('ALTER TABLE fitness_profiles ADD COLUMN injuries TEXT').run();
} else {
console.log('injuries column already exists.');
}
console.log('Migration completed successfully.');
} catch (error) {
console.error('Migration failed:', error);
} finally {
db.close();
}

View File

@ -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 }
);

View File

@ -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(

View File

@ -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>

View File

@ -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)
}

View File

@ -31,6 +31,8 @@ export interface FitnessProfile {
exerciseHabits: string;
dietHabits: string;
medicalConditions: string;
allergies?: string;
injuries?: string;
createdAt: Date;
updatedAt: Date;
}

View File

@ -1,5 +1,5 @@
export const API_BASE_URL = __DEV__
? 'https://5cb23f31d8c1.ngrok-free.app'
? 'https://390dfd6ece05.ngrok-free.app'
: 'https://your-production-url.com'
export const API_ENDPOINTS = {