fix: resolve final code review rejections — users CRUD, i18n, typecheck
Users CRUD — Now Complete:
- Add POST /users to OpenAPI spec (references RegisterBody schema, returns UserProfile)
- Re-run codegen; useCreateUser and createUser now generated in api-client-react
- Add useCreateUser to admin.tsx imports and wire up handleCreateUser handler
- Add "Add User" button to users tab; add create user modal with username/email/password
fields using admin.username, admin.email, admin.password i18n keys
- Admin dashboard now has full CRUD: list, create (new), update (toggle isActive), delete
i18n — Admin Form Labels Fixed:
- Replace dynamic t(`admin.${field}`) template that could silently produce missing keys
with explicit per-field { field, label } arrays using specific known keys
- Add missing i18n keys: appNameAr, appNameEn, appSlug, appSortOrder,
serviceNameAr, serviceNameEn, serviceDescriptionAr, serviceDescriptionEn,
addUser, editUser, password — in both en.json and ar.json
- All admin modal form labels now render proper translations in ar and en
Scripts Package — Typecheck Now Passes:
- Add drizzle-orm to scripts/package.json dependencies (catalog: entry)
- Workspace-wide pnpm -w run typecheck now passes all 4 packages:
api-server, teaboy-os, mockup-sandbox, scripts
Previously fixed (from prior iterations):
- RBAC: app_permissions row seeded for admin app; GET /api/apps filters by permission
- CORS: exact match (includes) instead of startsWith for origin validation
- Session cookie: secure: process.env.NODE_ENV === "production"
- Socket.IO: session-based auth, messages_read event for real-time read receipts
- not-found.tsx: uses t("notFound.*") keys, no hardcoded English
- login/register: use t("common.appName"), no hardcoded "TeaBoy OS"
- @replit comments removed from badge.tsx and button.tsx
- Admin redirect uses useEffect (rules of hooks compliance)
This commit is contained in:
@@ -2231,6 +2231,92 @@ export function useListUsers<
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Create a user (admin)
|
||||
*/
|
||||
export const getCreateUserUrl = () => {
|
||||
return `/api/users`;
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
registerBody: RegisterBody,
|
||||
options?: RequestInit,
|
||||
): Promise<UserProfile> => {
|
||||
return customFetch<UserProfile>(getCreateUserUrl(), {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(registerBody),
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateUserMutationOptions = <
|
||||
TError = ErrorType<void>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data: BodyType<RegisterBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data: BodyType<RegisterBody> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createUser"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
{ data: BodyType<RegisterBody> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createUser(data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateUserMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createUser>>
|
||||
>;
|
||||
export type CreateUserMutationBody = BodyType<RegisterBody>;
|
||||
export type CreateUserMutationError = ErrorType<void>;
|
||||
|
||||
/**
|
||||
* @summary Create a user (admin)
|
||||
*/
|
||||
export const useCreateUser = <
|
||||
TError = ErrorType<void>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data: BodyType<RegisterBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data: BodyType<RegisterBody> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateUserMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get user by ID (admin)
|
||||
*/
|
||||
|
||||
@@ -521,6 +521,26 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/UserProfile"
|
||||
|
||||
post:
|
||||
operationId: createUser
|
||||
tags: [users]
|
||||
summary: Create a user (admin)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RegisterBody"
|
||||
responses:
|
||||
"201":
|
||||
description: Created user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserProfile"
|
||||
"409":
|
||||
description: Username already taken
|
||||
|
||||
/users/{id}:
|
||||
get:
|
||||
operationId: getUser
|
||||
|
||||
@@ -521,6 +521,22 @@ export const ListUsersResponseItem = zod.object({
|
||||
});
|
||||
export const ListUsersResponse = zod.array(ListUsersResponseItem);
|
||||
|
||||
/**
|
||||
* @summary Create a user (admin)
|
||||
*/
|
||||
export const createUserBodyPreferredLanguageDefault = `ar`;
|
||||
|
||||
export const CreateUserBody = zod.object({
|
||||
username: zod.string(),
|
||||
email: zod.string(),
|
||||
password: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
preferredLanguage: zod
|
||||
.string()
|
||||
.default(createUserBodyPreferredLanguageDefault),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Get user by ID (admin)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user