mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-23 01:54:37 +03:00
WIP: Enables strict null checks in SDK
This commit is contained in:
parent
c350cbe6e4
commit
ddd10d1733
@ -77,7 +77,7 @@ window.addEventListener('storage', (event) => {
|
||||
* standard format to be further used by the client. It is also assumed that given API
|
||||
* error has been formatted as implemented by HttpError on the server.
|
||||
*/
|
||||
export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): void {
|
||||
export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): AxiosError | WaspHttpError {
|
||||
if (error?.response) {
|
||||
// If error came from HTTP response, we capture most informative message
|
||||
// and also add .statusCode information to it.
|
||||
@ -88,10 +88,10 @@ export function handleApiError(error: AxiosError<{ message?: string, data?: unkn
|
||||
// That would require copying HttpError code to web-app also and using it here.
|
||||
const responseJson = error.response?.data
|
||||
const responseStatusCode = error.response.status
|
||||
throw new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson)
|
||||
return new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson)
|
||||
} else {
|
||||
// If any other error, we just propagate it.
|
||||
throw error
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,6 @@ export async function login(data: { email: string; password: string }): Promise<
|
||||
const response = await api.post('{= loginPath =}', data);
|
||||
await initSession(response.data.sessionId);
|
||||
} catch (e) {
|
||||
handleApiError(e);
|
||||
throw handleApiError(e);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export async function requestPasswordReset(data: { email: string; }): Promise<{
|
||||
const response = await api.post('{= requestPasswordResetPath =}', data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
handleApiError(e);
|
||||
throw handleApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,6 @@ export async function resetPassword(data: { token: string; password: string; }):
|
||||
const response = await api.post('{= resetPasswordPath =}', data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
handleApiError(e);
|
||||
throw handleApiError(e);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ export async function signup(data: { email: string; password: string }): Promise
|
||||
const response = await api.post('{= signupPath =}', data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
handleApiError(e);
|
||||
throw handleApiError(e);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ export async function verifyEmail(data: {
|
||||
const response = await api.post('{= verifyEmailPath =}', data)
|
||||
return response.data
|
||||
} catch (e) {
|
||||
handleApiError(e)
|
||||
throw handleApiError(e)
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ function AdditionalFormFields({
|
||||
}: {
|
||||
hookForm: UseFormReturn<LoginSignupFormFields>;
|
||||
formState: FormState;
|
||||
additionalSignupFields: AdditionalSignupFields;
|
||||
additionalSignupFields?: AdditionalSignupFields;
|
||||
}) {
|
||||
const {
|
||||
register,
|
||||
@ -302,7 +302,7 @@ function AdditionalFormFields({
|
||||
disabled={isLoading}
|
||||
/>
|
||||
{errors[field.name] && (
|
||||
<FormError>{errors[field.name].message}</FormError>
|
||||
<FormError>{errors[field.name]!.message}</FormError>
|
||||
)}
|
||||
</FormItemGroup>
|
||||
);
|
||||
@ -341,7 +341,7 @@ function isFieldRenderFn(
|
||||
}
|
||||
|
||||
function areAdditionalFieldsRenderFn(
|
||||
additionalSignupFields: AdditionalSignupFields
|
||||
additionalSignupFields?: AdditionalSignupFields
|
||||
): additionalSignupFields is AdditionalSignupFieldRenderFn {
|
||||
return typeof additionalSignupFields === 'function'
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ export default async function login(username: string, password: string): Promise
|
||||
|
||||
await initSession(response.data.sessionId)
|
||||
} catch (error) {
|
||||
handleApiError(error)
|
||||
throw handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ async function getAuthUserData(userId: {= userEntityUpper =}['id']): Promise<Aut
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
return createAuthUserData(user);
|
||||
return createAuthUserData(user!);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
@ -5,6 +5,6 @@ export default async function signup(userFields: { username: string; password: s
|
||||
try {
|
||||
await api.post('{= signupPath =}', userFields)
|
||||
} catch (error) {
|
||||
handleApiError(error)
|
||||
throw handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ function createUserGetter(): Query<void, AuthUser | null> {
|
||||
try {
|
||||
const response = await api.get(getMeRoute.path)
|
||||
const userData = superjsonDeserialize<AuthUserData | null>(response.data)
|
||||
// TODO: figure out why overloading is not working
|
||||
// @ts-ignore
|
||||
return makeAuthUserIfPossible(userData)
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
return null
|
||||
} else {
|
||||
handleApiError(error)
|
||||
throw handleApiError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,12 +46,12 @@ function makeAuthUser(data: AuthUserData): AuthUser {
|
||||
...data,
|
||||
getFirstProviderUserId: () => {
|
||||
const identities = Object.values(data.identities).filter(Boolean);
|
||||
return identities.length > 0 ? identities[0].id : null;
|
||||
return identities.length > 0 ? identities[0]!.id : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): UserEntityWithAuth['auth']['identities'][number] | null {
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): NonNullable<UserEntityWithAuth['auth']>['identities'][number] | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
|
@ -125,13 +125,13 @@ export async function updateAuthIdentityProviderData<PN extends ProviderName>(
|
||||
}
|
||||
|
||||
type FindAuthWithUserResult = {= authEntityUpper =} & {
|
||||
{= userFieldOnAuthEntityName =}: {= userEntityUpper =}
|
||||
{= userFieldOnAuthEntityName =}: {= userEntityUpper =} | null
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export async function findAuthWithUserBy(
|
||||
where: Prisma.{= authEntityUpper =}WhereInput
|
||||
): Promise<FindAuthWithUserResult> {
|
||||
): Promise<FindAuthWithUserResult | null> {
|
||||
return prisma.{= authEntityLower =}.findFirst({ where, include: { {= userFieldOnAuthEntityName =}: true }});
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ export async function createUser(
|
||||
serializedProviderData?: string,
|
||||
userFields?: PossibleUserFields,
|
||||
): Promise<{= userEntityUpper =} & {
|
||||
auth: {= authEntityUpper =}
|
||||
auth: {= authEntityUpper =} | null
|
||||
}> {
|
||||
return prisma.{= userEntityLower =}.create({
|
||||
data: {
|
||||
@ -269,6 +269,8 @@ export function deserializeAndSanitizeProviderData<PN extends ProviderName>(
|
||||
let data = JSON.parse(providerData) as PossibleProviderData[PN];
|
||||
|
||||
if (providerDataHasPasswordField(data) && shouldRemovePasswordField) {
|
||||
// TODO: fix this type
|
||||
// @ts-ignore
|
||||
delete data.hashedPassword;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ const EMAIL_FIELD = 'email';
|
||||
const TOKEN_FIELD = 'token';
|
||||
|
||||
// PUBLIC API
|
||||
export function ensureValidEmail(args: unknown): void {
|
||||
export function ensureValidEmail<Args extends object>(args: Args): void {
|
||||
validate(args, [
|
||||
{ validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email },
|
||||
{ validates: EMAIL_FIELD, message: 'email must be a valid email', validator: email => isValidEmail(email) },
|
||||
@ -14,21 +14,21 @@ export function ensureValidEmail(args: unknown): void {
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function ensureValidUsername(args: unknown): void {
|
||||
export function ensureValidUsername<Args extends object>(args: Args): void {
|
||||
validate(args, [
|
||||
{ validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username }
|
||||
]);
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function ensurePasswordIsPresent(args: unknown): void {
|
||||
export function ensurePasswordIsPresent<Args extends object>(args: Args): void {
|
||||
validate(args, [
|
||||
{ validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password },
|
||||
]);
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function ensureValidPassword(args: unknown): void {
|
||||
export function ensureValidPassword<Args extends object>(args: Args): void {
|
||||
validate(args, [
|
||||
{ validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) },
|
||||
{ validates: PASSWORD_FIELD, message: 'password must contain a number', validator: password => containsNumber(password) },
|
||||
@ -36,7 +36,7 @@ export function ensureValidPassword(args: unknown): void {
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function ensureTokenIsPresent(args: unknown): void {
|
||||
export function ensureTokenIsPresent<Args extends object>(args: Args): void {
|
||||
validate(args, [
|
||||
{ validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token },
|
||||
]);
|
||||
@ -47,7 +47,7 @@ export function throwValidationError(message: string): void {
|
||||
throw new HttpError(422, 'Validation failed', { message })
|
||||
}
|
||||
|
||||
function validate(args: unknown, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void {
|
||||
function validate<Args extends object>(args: Args, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void {
|
||||
for (const { validates, message, validator } of validators) {
|
||||
if (!validator(args[validates])) {
|
||||
throwValidationError(message);
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{={= =}=}}
|
||||
import { stripTrailingSlash } from 'wasp/universal/url'
|
||||
import { stripTrailingSlash } from '../universal/url.js'
|
||||
|
||||
const apiUrl = stripTrailingSlash(import.meta.env.REACT_APP_API_URL) || '{= defaultServerUrl =}';
|
||||
|
||||
|
@ -170,7 +170,7 @@ function translateToInternalDefinition<Item, CachedData>(
|
||||
): InternalOptimisticUpdateDefinition<Item, CachedData> {
|
||||
const { getQuerySpecifier, updateQuery } = publicOptimisticUpdateDefinition;
|
||||
|
||||
const definitionErrors = [];
|
||||
const definitionErrors: string[] = [];
|
||||
if (typeof getQuerySpecifier !== "function") {
|
||||
definitionErrors.push("`getQuerySpecifier` is not a function.");
|
||||
}
|
||||
@ -207,9 +207,11 @@ function makeOptimisticUpdateMutationFn<Input, Output, CachedData>(
|
||||
return (function performActionWithOptimisticUpdates(item?: Input) {
|
||||
const specificOptimisticUpdateDefinitions = optimisticUpdateDefinitions.map(
|
||||
(generalDefinition) =>
|
||||
// @ts-ignore
|
||||
getOptimisticUpdateDefinitionForSpecificItem(generalDefinition, item)
|
||||
);
|
||||
return (actionFn as InternalAction<Input, Output>).internal(
|
||||
// @ts-ignore
|
||||
item,
|
||||
specificOptimisticUpdateDefinitions
|
||||
);
|
||||
@ -262,11 +264,13 @@ function makeRqOptimisticUpdateOptions<ActionInput, CachedData>(
|
||||
const previousData = new Map();
|
||||
specificOptimisticUpdateDefinitions.forEach(({ queryKey, updateQuery }) => {
|
||||
// Snapshot the currently cached value.
|
||||
// @ts-ignore
|
||||
const previousDataForQuery: CachedData =
|
||||
queryClient.getQueryData(queryKey);
|
||||
|
||||
// Attempt to optimistically update the cache using the new value.
|
||||
try {
|
||||
// @ts-ignore
|
||||
queryClient.setQueryData(queryKey, updateQuery);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
|
@ -15,7 +15,7 @@ export async function callOperation(operationRoute: OperationRoute, args: any) {
|
||||
const response = await api.post(operationRoute.path, superjsonArgs)
|
||||
return superjsonDeserialize(response.data)
|
||||
} catch (error) {
|
||||
handleApiError(error)
|
||||
throw handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
// Details here: https://github.com/wasp-lang/wasp/issues/2017
|
||||
export function makeQueryCacheKey<Input, Output>(
|
||||
query: Query<Input, Output>,
|
||||
payload: Input
|
||||
payload?: Input
|
||||
): (string | Input)[] {
|
||||
return payload !== undefined ?
|
||||
[...query.queryCacheKey, payload]
|
||||
|
@ -93,4 +93,5 @@ type ClientOperationWithNonAnyInput<Input, Output> =
|
||||
? (args?: unknown) => Promise<Output>
|
||||
: [Input] extends [void]
|
||||
? () => Promise<Output>
|
||||
: (args: Input) => Promise<Output>
|
||||
// TODO: decide if this is what we want?
|
||||
: (args?: Input) => Promise<Output>
|
||||
|
@ -31,8 +31,8 @@ const auth = handleRejection(async (req, res, next) => {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
req.sessionId = sessionAndUser.session.id
|
||||
req.user = sessionAndUser.user
|
||||
req.sessionId = sessionAndUser!.session.id
|
||||
req.user = sessionAndUser!.user
|
||||
|
||||
next()
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ export class HttpError extends Error {
|
||||
public statusCode: number
|
||||
public data: unknown
|
||||
|
||||
constructor (statusCode: number, message?: string, data?: Record<string, unknown>, ...params: unknown[]) {
|
||||
constructor (statusCode: number, message?: string, data?: Record<string, unknown>, ...params: any[]) {
|
||||
super(message, ...params)
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
|
@ -64,7 +64,13 @@ async function sendEmailAndSaveMetadata(
|
||||
// so the user can't send multiple requests while the email is being sent.
|
||||
const providerId = createProviderId("email", email);
|
||||
const authIdentity = await findAuthIdentity(providerId);
|
||||
const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity.providerData);
|
||||
|
||||
|
||||
if (!authIdentity) {
|
||||
throw new Error(`User with email: ${email} not found.`);
|
||||
}
|
||||
|
||||
const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity!.providerData);
|
||||
await updateAuthIdentityProviderData<'email'>(providerId, providerData, metadata);
|
||||
|
||||
emailSender.send(content).catch((e) => {
|
||||
|
@ -100,7 +100,7 @@ type OnBeforeLoginHookParams = {
|
||||
/**
|
||||
* User that is trying to log in.
|
||||
*/
|
||||
user: Awaited<ReturnType<typeof findAuthWithUserBy>>['user']
|
||||
user: NonNullable<Awaited<ReturnType<typeof findAuthWithUserBy>>>['user']
|
||||
/**
|
||||
* Request object that can be used to access the incoming request.
|
||||
*/
|
||||
@ -115,7 +115,7 @@ type OnAfterLoginHookParams = {
|
||||
/**
|
||||
* User that is logged in.
|
||||
*/
|
||||
user: Awaited<ReturnType<typeof findAuthWithUserBy>>['user']
|
||||
user: NonNullable<Awaited<ReturnType<typeof findAuthWithUserBy>>>['user']
|
||||
/**
|
||||
* OAuth flow data that was generated during the OAuth flow. This is only
|
||||
* available if the user logged in using OAuth.
|
||||
|
@ -39,5 +39,5 @@ function getRedirectUriForError(error: string): URL {
|
||||
}
|
||||
|
||||
function isHttpErrorWithExtraMessage(error: HttpError): error is HttpError & { data: { message: string } } {
|
||||
return error.data && typeof (error.data as any).message === 'string';
|
||||
return error.data ? typeof (error.data as any).message === 'string' : false;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{{={= =}=}}
|
||||
import merge from 'lodash.merge'
|
||||
|
||||
import { stripTrailingSlash } from "wasp/universal/url";
|
||||
import { stripTrailingSlash } from "../universal/url.js";
|
||||
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
const nodeEnv = process.env.NODE_ENV ?? 'development'
|
||||
|
||||
// TODO:
|
||||
// - Use dotenv library to consume env vars from a file.
|
||||
@ -39,9 +39,9 @@ const config: {
|
||||
production: EnvConfig,
|
||||
} = {
|
||||
all: {
|
||||
env,
|
||||
isDevelopment: env === 'development',
|
||||
port: parseInt(process.env.PORT) || {= defaultServerPort =},
|
||||
env: nodeEnv,
|
||||
isDevelopment: nodeEnv === 'development',
|
||||
port: process.env.PORT ? parseInt(process.env.PORT) : {= defaultServerPort =},
|
||||
databaseUrl: process.env.{= databaseUrlEnvVarName =},
|
||||
allowedCORSOrigins: [],
|
||||
{=# isAuthEnabled =}
|
||||
@ -54,15 +54,17 @@ const config: {
|
||||
production: getProductionConfig(),
|
||||
}
|
||||
|
||||
const resolvedConfig: Config = merge(config.all, config[env])
|
||||
const resolvedConfig: Config = merge(config.all, config[nodeEnv])
|
||||
// PUBLIC API
|
||||
export default resolvedConfig
|
||||
|
||||
function getDevelopmentConfig(): EnvConfig {
|
||||
const frontendUrl = stripTrailingSlash(process.env.WASP_WEB_CLIENT_URL || '{= defaultClientUrl =}');
|
||||
const serverUrl = stripTrailingSlash(process.env.WASP_SERVER_URL || '{= defaultServerUrl =}');
|
||||
const frontendUrl = stripTrailingSlash(process.env.WASP_WEB_CLIENT_URL) ?? '{= defaultClientUrl =}';
|
||||
const serverUrl = stripTrailingSlash(process.env.WASP_SERVER_URL) ?? '{= defaultServerUrl =}';
|
||||
return {
|
||||
// @ts-ignore
|
||||
frontendUrl,
|
||||
// @ts-ignore
|
||||
serverUrl,
|
||||
allowedCORSOrigins: '*',
|
||||
{=# isAuthEnabled =}
|
||||
@ -77,8 +79,11 @@ function getProductionConfig(): EnvConfig {
|
||||
const frontendUrl = stripTrailingSlash(process.env.WASP_WEB_CLIENT_URL);
|
||||
const serverUrl = stripTrailingSlash(process.env.WASP_SERVER_URL);
|
||||
return {
|
||||
// @ts-ignore
|
||||
frontendUrl,
|
||||
// @ts-ignore
|
||||
serverUrl,
|
||||
// @ts-ignore
|
||||
allowedCORSOrigins: [frontendUrl],
|
||||
{=# isAuthEnabled =}
|
||||
auth: {
|
||||
|
@ -5,10 +5,12 @@ import { EmailSender } from "./core/types.js";
|
||||
{=# isSmtpProviderUsed =}
|
||||
const emailProvider = {
|
||||
type: "smtp",
|
||||
host: process.env.SMTP_HOST,
|
||||
// TODO: We'll validate this
|
||||
host: process.env.SMTP_HOST!,
|
||||
// @ts-ignore
|
||||
port: parseInt(process.env.SMTP_PORT, 10),
|
||||
username: process.env.SMTP_USERNAME,
|
||||
password: process.env.SMTP_PASSWORD,
|
||||
username: process.env.SMTP_USERNAME!,
|
||||
password: process.env.SMTP_PASSWORD!,
|
||||
} as const;
|
||||
{=/ isSmtpProviderUsed =}
|
||||
{=# isSendGridProviderUsed =}
|
||||
|
@ -16,6 +16,8 @@ export type {= typeName =}<Input extends JSONObject, Output extends JSONValue |
|
||||
export const {= jobName =} = createJobDefinition({
|
||||
jobName: '{= jobName =}',
|
||||
defaultJobOptions: {=& jobPerformOptions =},
|
||||
// TODO: output job schedule args as undefined if not provided
|
||||
// @ts-ignore
|
||||
jobSchedule: {=& jobSchedule =},
|
||||
entities,
|
||||
})
|
||||
|
@ -39,6 +39,7 @@ export function createJobDefinition<
|
||||
}) {
|
||||
return new PgBossJob<Input, Output, Entities>(
|
||||
jobName,
|
||||
// @ts-ignore
|
||||
defaultJobOptions,
|
||||
entities,
|
||||
jobSchedule,
|
||||
@ -90,7 +91,7 @@ export function registerJob<
|
||||
await boss.schedule(
|
||||
job.jobName,
|
||||
job.jobSchedule.cron,
|
||||
job.jobSchedule.args || null,
|
||||
job.jobSchedule.args,
|
||||
options
|
||||
)
|
||||
}
|
||||
@ -107,8 +108,8 @@ class PgBossJob<
|
||||
Output extends JSONValue | void,
|
||||
Entities extends Partial<PrismaDelegate>
|
||||
> extends Job {
|
||||
public readonly defaultJobOptions: Parameters<PgBoss['send']>[2]
|
||||
public readonly startAfter: number | string | Date
|
||||
public readonly defaultJobOptions?: Parameters<PgBoss['send']>[2]
|
||||
public readonly startAfter: number | string | Date | undefined
|
||||
public readonly entities: Entities
|
||||
public readonly jobSchedule: JobSchedule | null
|
||||
|
||||
@ -128,6 +129,7 @@ class PgBossJob<
|
||||
delay(startAfter: number | string | Date) {
|
||||
return new PgBossJob<Input, Output, Entities>(
|
||||
this.jobName,
|
||||
// @ts-ignore
|
||||
this.defaultJobOptions,
|
||||
this.entities,
|
||||
this.jobSchedule,
|
||||
|
@ -14,11 +14,11 @@
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
// See https://github.com/wasp-lang/wasp/issues/2056 before activating this:
|
||||
// "useUnknownInCatchVariables": true,
|
||||
// The following 3 stict options will require more work:
|
||||
// "noImplicitAny": true,
|
||||
// "strictNullChecks": true,
|
||||
// "strictPropertyInitialization": true,
|
||||
// Overriding this because we want to use top-level await
|
||||
"module": "esnext",
|
||||
|
@ -1,15 +1,20 @@
|
||||
import "./Main.css";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Link } from "wasp/client/router";
|
||||
import './Main.css'
|
||||
import React from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Link } from 'wasp/client/router'
|
||||
|
||||
import { tasks as tasksCrud } from "wasp/client/crud";
|
||||
import { tasks as tasksCrud } from 'wasp/client/crud'
|
||||
|
||||
const DetailPage = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { data: task, isLoading } = tasksCrud.get.useQuery({
|
||||
id: parseInt(id, 10),
|
||||
});
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const { data: task, isLoading } = tasksCrud.get.useQuery(
|
||||
{
|
||||
id: parseInt(id!, 10),
|
||||
},
|
||||
{
|
||||
enabled: !!id,
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
@ -30,7 +35,7 @@ const DetailPage = () => {
|
||||
<Link to="/">Return</Link>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailPage;
|
||||
export default DetailPage
|
||||
|
@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
import { getMe } from 'wasp/client/auth'
|
||||
import {
|
||||
|
Loading…
Reference in New Issue
Block a user