Driver onboarding now photographs the licence, ID card and vehicle
registration and reads the credential fields off them, plus a camera-only
profile selfie riders check the arriving driver against. Adds in-app chat
and WebRTC calls, push-backed ride offers, ratings, cancellation and
payment sheets, settlement, and the owner dashboard endpoints behind them.
Camera permission on Android:
- Declare CAMERA and READ_MEDIA_IMAGES in the manifest. expo-image-picker's
own plugin never declares CAMERA, and Android denies a request for an
undeclared permission instantly and silently — no dialog is ever shown,
which is indistinguishable from the app not asking at all.
- Handle canAskAgain: once Android stops showing the dialog, repeating why
we need it is a dead end, so offer Open Settings instead (lib/capture-
permission.ts), matching what the location flow already did.
Session: a 401 on a request that carried a token now ends the session
instead of being reinterpreted per-screen — driver-home had been reading it
as "this user has no driver profile" and showing an onboarding form to an
already-onboarded driver. Requests without a token are exempt so a failed
sign-in doesn't sign you out, and the notification is latched per token so
concurrent polls tear the session down once. (root) gains the auth guard
that turns that into the sign-in screen; app/index.tsx only guarded the way
in, leaving a session that ended mid-screen with nowhere to go.
Also ignore .uploads/ — it holds driver licence, ID and vehicle scans plus
profile photos, which are personal data and must not be committed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { requireAuth } from "@/lib/jwt";
|
|
import { sql } from "@/lib/db";
|
|
|
|
export async function GET(req: Request) {
|
|
const auth = requireAuth(req);
|
|
if ("error" in auth) return auth.error;
|
|
|
|
try {
|
|
const response = await sql`
|
|
SELECT id, name, email, phone, role FROM users WHERE id = ${auth.userId}
|
|
`;
|
|
|
|
return Response.json({ data: response[0] ?? null });
|
|
} catch (error) {
|
|
console.log("[GET_USER]: ", error);
|
|
|
|
return Response.json({ error }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// PATCH — one-time role selection, straight after sign-up.
|
|
//
|
|
// The role is write-once. It used to be freely re-assignable, which meant any
|
|
// account could flip itself to 'driver' on demand; combined with self-service
|
|
// onboarding that was a rider account away from receiving live pickups. Role
|
|
// is no longer a credential on its own (driver profiles are vetted), but it
|
|
// still shouldn't be a toggle: a user who genuinely needs to switch goes
|
|
// through support, which leaves a record. Re-sending the same role is a no-op
|
|
// so a retried request from the role screen still succeeds.
|
|
export async function PATCH(req: Request) {
|
|
const auth = requireAuth(req);
|
|
if ("error" in auth) return auth.error;
|
|
|
|
const { role } = await req.json();
|
|
|
|
if (!["rider", "driver"].includes(role)) {
|
|
return Response.json({ error: "Invalid role." }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
const response = await sql`
|
|
UPDATE users SET role = ${role}
|
|
WHERE id = ${auth.userId}
|
|
AND (role IS NULL OR role = ${role})
|
|
RETURNING id, role
|
|
`;
|
|
|
|
if (response.length === 0) {
|
|
const existing = await sql<{ role: string | null }>`
|
|
SELECT role FROM users WHERE id = ${auth.userId}
|
|
`;
|
|
|
|
if (!existing[0]) {
|
|
return Response.json({ error: "User not found." }, { status: 404 });
|
|
}
|
|
|
|
return Response.json(
|
|
{
|
|
error: "Your account role has already been set.",
|
|
code: "ROLE_ALREADY_SET",
|
|
role: existing[0].role,
|
|
},
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
|
|
return Response.json({ data: response[0] });
|
|
} catch (error) {
|
|
console.log("[PATCH_USER]: ", error);
|
|
|
|
return Response.json({ error }, { status: 500 });
|
|
}
|
|
}
|