Merge branch 'develop' into MED-157

This commit is contained in:
Danel Kungla
2025-09-23 11:03:54 +03:00
578 changed files with 17422 additions and 9960 deletions

View File

@@ -2,4 +2,4 @@
@kit/analytics Package provides a simple and consistent API for tracking analytics events in web applications.
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/analytics/analytics-and-events).
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/analytics/analytics-and-events).

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,17 +4,12 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.15.18"
},

View File

@@ -6,9 +6,9 @@ Make sure the app installs the `@kit/billing` package before using it.
```json
{
"name": "my-app",
"dependencies": {
"@kit/billing": "*"
}
"name": "my-app",
"dependencies": {
"@kit/billing": "*"
}
}
```
```

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,11 +4,8 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./components/*": "./src/components/*",
@@ -16,8 +13,6 @@
"./types": "./src/types/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*"

View File

@@ -25,21 +25,32 @@ export const LineItemSchema = z
.describe('Unique identifier for the line item. Defined by the Provider.')
.min(1),
name: z
.string().describe('Name of the line item. Displayed to the user.')
.string()
.describe('Name of the line item. Displayed to the user.')
.min(1),
description: z
.string().describe('Description of the line item. Displayed to the user and will replace the auto-generated description inferred' +
' from the line item. This is useful if you want to provide a more detailed description to the user.')
.string()
.describe(
'Description of the line item. Displayed to the user and will replace the auto-generated description inferred' +
' from the line item. This is useful if you want to provide a more detailed description to the user.',
)
.optional(),
cost: z
.number().describe('Cost of the line item. Displayed to the user.')
.number()
.describe('Cost of the line item. Displayed to the user.')
.min(0),
type: LineItemTypeSchema,
unit: z
.string().describe('Unit of the line item. Displayed to the user. Example "seat" or "GB"')
.string()
.describe(
'Unit of the line item. Displayed to the user. Example "seat" or "GB"',
)
.optional(),
setupFee: z
.number().describe(`Lemon Squeezy only: If true, in addition to the cost, a setup fee will be charged.`)
.number()
.describe(
`Lemon Squeezy only: If true, in addition to the cost, a setup fee will be charged.`,
)
.positive()
.optional(),
tiers: z
@@ -78,10 +89,12 @@ export const LineItemSchema = z
export const PlanSchema = z
.object({
id: z
.string().describe('Unique identifier for the plan. Defined by yourself.')
.string()
.describe('Unique identifier for the plan. Defined by yourself.')
.min(1),
name: z
.string().describe('Name of the plan. Displayed to the user.')
.string()
.describe('Name of the plan. Displayed to the user.')
.min(1),
interval: BillingIntervalSchema.optional(),
custom: z.boolean().default(false).optional(),
@@ -106,7 +119,10 @@ export const PlanSchema = z
},
),
trialDays: z
.number().describe('Number of days for the trial period. Leave empty for no trial.')
.number()
.describe(
'Number of days for the trial period. Leave empty for no trial.',
)
.positive()
.optional(),
paymentType: PaymentTypeSchema,
@@ -188,34 +204,43 @@ export const PlanSchema = z
const ProductSchema = z
.object({
id: z
.string().describe('Unique identifier for the product. Defined by th Provider.')
.string()
.describe('Unique identifier for the product. Defined by th Provider.')
.min(1),
name: z
.string().describe('Name of the product. Displayed to the user.')
.string()
.describe('Name of the product. Displayed to the user.')
.min(1),
description: z
.string().describe('Description of the product. Displayed to the user.')
.string()
.describe('Description of the product. Displayed to the user.')
.min(1),
currency: z
.string().describe('Currency code for the product. Displayed to the user.')
.string()
.describe('Currency code for the product. Displayed to the user.')
.min(3)
.max(3),
badge: z
.string().describe('Badge for the product. Displayed to the user. Example: "Popular"')
.string()
.describe(
'Badge for the product. Displayed to the user. Example: "Popular"',
)
.optional(),
features: z
.array(
z.string(),
).describe('Features of the product. Displayed to the user.')
.array(z.string())
.describe('Features of the product. Displayed to the user.')
.nonempty(),
enableDiscountField: z
.boolean().describe('Enable discount field for the product in the checkout.')
.boolean()
.describe('Enable discount field for the product in the checkout.')
.optional(),
highlighted: z
.boolean().describe('Highlight this product. Displayed to the user.')
.boolean()
.describe('Highlight this product. Displayed to the user.')
.optional(),
hidden: z
.boolean().describe('Hide this product from being displayed to users.')
.boolean()
.describe('Hide this product from being displayed to users.')
.optional(),
plans: z.array(PlanSchema),
})

View File

@@ -1,7 +1,11 @@
import { z } from 'zod';
export const ReportBillingUsageSchema = z.object({
id: z.string().describe('The id of the usage record. For Stripe a customer ID, for LS a subscription item ID.'),
id: z
.string()
.describe(
'The id of the usage record. For Stripe a customer ID, for LS a subscription item ID.',
),
eventName: z
.string()
.describe('The name of the event that triggered the usage')

View File

@@ -24,9 +24,7 @@ export interface IHandleWebhookEventParams {
// this method is called when an invoice is paid. We don't have a specific use case for this
// but it's extremely common for credit-based systems
onInvoicePaid: (
subscription: UpsertSubscriptionParams,
) => Promise<unknown>;
onInvoicePaid: (subscription: UpsertSubscriptionParams) => Promise<unknown>;
// generic handler for any event
onEvent?: (data: unknown) => Promise<unknown>;

View File

@@ -1,3 +1,3 @@
# Billing - @kit/billing-gateway
This package is responsible for handling all billing related operations. It is a gateway to the billing service.
This package is responsible for handling all billing related operations. It is a gateway to the billing service.

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,11 +4,8 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./components": "./src/components/index.ts",
@@ -18,9 +15,7 @@
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@kit/billing": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/lemon-squeezy": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/stripe": "workspace:*",
"@kit/montonio": "workspace:*",

View File

@@ -107,7 +107,7 @@ function BlurryBackdrop() {
return (
<div
className={
'bg-background/30 fixed left-0 top-0 w-full backdrop-blur-sm' +
'bg-background/30 fixed top-0 left-0 w-full backdrop-blur-sm' +
' !m-0 h-full'
}
/>

View File

@@ -317,7 +317,7 @@ export function PlanPicker(
<div
className={
'flex flex-col gap-y-3 lg:flex-row lg:items-center lg:space-x-4 lg:space-y-0 lg:text-right'
'flex flex-col gap-y-3 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-4 lg:text-right'
}
>
<div>

View File

@@ -2,4 +2,4 @@
This package is responsible for handling all billing related operations using Lemon Squeezy.
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/billing/lemon-squeezy).
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/billing/lemon-squeezy).

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,11 +4,8 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./components": "./src/components/index.ts"
@@ -18,8 +15,6 @@
},
"devDependencies": {
"@kit/billing": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",

View File

@@ -1,4 +1,3 @@
# Billing / Montonio - @kit/montonio
This package is responsible for handling all billing related operations using Montonio.

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,18 +4,13 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/billing": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",

View File

@@ -1,6 +1,5 @@
import { z } from 'zod';
export const MontonioClientEnvSchema = z
.object({
accessKey: z.string().min(1),
});
export const MontonioClientEnvSchema = z.object({
accessKey: z.string().min(1),
});

View File

@@ -1,15 +1,14 @@
import { z } from 'zod';
export const MontonioServerEnvSchema = z
.object({
secretKey: z
.string({
error: `Please provide the variable MONTONIO_SECRET_KEY`,
})
.min(1),
apiUrl: z
.string({
error: `Please provide the variable MONTONIO_API_URL`,
})
.min(1),
});
export const MontonioServerEnvSchema = z.object({
secretKey: z
.string({
error: `Please provide the variable MONTONIO_SECRET_KEY`,
})
.min(1),
apiUrl: z
.string({
error: `Please provide the variable MONTONIO_API_URL`,
})
.min(1),
});

View File

@@ -1,16 +1,19 @@
import jwt from 'jsonwebtoken';
import axios, { AxiosError } from 'axios';
import jwt from 'jsonwebtoken';
import { MontonioClientEnvSchema } from '../schema/montonio-client-env.schema';
import { MontonioServerEnvSchema } from '../schema/montonio-server-env.schema';
const clientEnv = () => MontonioClientEnvSchema.parse({
accessKey: process.env.NEXT_PUBLIC_MONTONIO_ACCESS_KEY,
});
const clientEnv = () =>
MontonioClientEnvSchema.parse({
accessKey: process.env.NEXT_PUBLIC_MONTONIO_ACCESS_KEY,
});
const serverEnv = () => MontonioServerEnvSchema.parse({
apiUrl: process.env.MONTONIO_API_URL,
secretKey: process.env.MONTONIO_SECRET_KEY,
});
const serverEnv = () =>
MontonioServerEnvSchema.parse({
apiUrl: process.env.MONTONIO_API_URL,
secretKey: process.env.MONTONIO_SECRET_KEY,
});
export class MontonioOrderHandlerService {
public async getMontonioPaymentLink({
@@ -45,23 +48,27 @@ export class MontonioOrderHandlerService {
returnUrl,
askAdditionalInfo: false,
merchantReference,
type: "one_time",
type: 'one_time',
};
const token = jwt.sign(params, secretKey, {
algorithm: "HS256",
expiresIn: "10m",
algorithm: 'HS256',
expiresIn: '10m',
});
try {
const { data } = await axios.post(`${apiUrl}/api/payment-links`, { data: token });
const { data } = await axios.post(`${apiUrl}/api/payment-links`, {
data: token,
});
return data.url;
} catch (error) {
if (error instanceof AxiosError) {
console.error(error.response?.data);
}
console.error(`Failed to create payment link, params=${JSON.stringify(params)}`, error);
throw new Error("Failed to create payment link");
console.error(
`Failed to create payment link, params=${JSON.stringify(params)}`,
error,
);
throw new Error('Failed to create payment link');
}
}
}

View File

@@ -1,7 +1,12 @@
import type { BillingWebhookHandlerService, IHandleWebhookEventParams } from '@kit/billing';
import jwt from 'jsonwebtoken';
import type {
BillingWebhookHandlerService,
IHandleWebhookEventParams,
} from '@kit/billing';
import { getLogger } from '@kit/shared/logger';
import { Database, Enums } from '@kit/supabase/database';
import jwt from 'jsonwebtoken';
import { MontonioServerEnvSchema } from '../schema/montonio-server-env.schema';
type UpsertOrderParams =
@@ -10,20 +15,26 @@ type UpsertOrderParams =
type BillingProvider = Enums<{ schema: 'medreport' }, 'billing_provider'>;
interface MontonioOrderToken {
uuid: string;
accessKey: string;
merchantReference: string;
merchantReferenceDisplay: string;
paymentStatus: 'PAID' | 'FAILED' | 'CANCELLED' | 'PENDING' | 'EXPIRED' | 'REFUNDED';
paymentMethod: string;
grandTotal: number;
currency: string;
senderIban?: string;
senderName?: string;
paymentProviderName?: string;
paymentLinkUuid: string;
iat: number;
exp: number;
uuid: string;
accessKey: string;
merchantReference: string;
merchantReferenceDisplay: string;
paymentStatus:
| 'PAID'
| 'FAILED'
| 'CANCELLED'
| 'PENDING'
| 'EXPIRED'
| 'REFUNDED';
paymentMethod: string;
grandTotal: number;
currency: string;
senderIban?: string;
senderName?: string;
paymentProviderName?: string;
paymentLinkUuid: string;
iat: number;
exp: number;
}
const { secretKey } = MontonioServerEnvSchema.parse({
@@ -39,22 +50,25 @@ export class MontonioWebhookHandlerService
async verifyWebhookSignature(request: Request) {
const logger = await getLogger();
let token: string;
try {
const url = new URL(request.url);
const searchParams = url.searchParams;
console.info("searchParams", searchParams, url);
console.info('searchParams', searchParams, url);
const tokenParam = searchParams.get('order-token') as string | null;
if (!tokenParam) {
throw new Error('Missing order-token');
}
token = tokenParam;
} catch (error) {
logger.error({
error,
name: this.namespace,
}, `Failed to parse Montonio webhook request`);
logger.error(
{
error,
name: this.namespace,
},
`Failed to parse Montonio webhook request`,
);
throw new Error('Invalid request');
}
@@ -64,24 +78,30 @@ export class MontonioWebhookHandlerService
});
return decoded as MontonioOrderToken;
} catch (error) {
logger.error({
error,
name: this.namespace,
}, `Failed to verify Montonio webhook signature`);
logger.error(
{
error,
name: this.namespace,
},
`Failed to verify Montonio webhook signature`,
);
throw new Error('Invalid signature');
}
}
async handleWebhookEvent(
event: MontonioOrderToken,
params: IHandleWebhookEventParams
params: IHandleWebhookEventParams,
) {
const logger = await getLogger();
logger.info({
name: this.namespace,
event,
}, `Received Montonio webhook event`);
logger.info(
{
name: this.namespace,
event,
},
`Received Montonio webhook event`,
);
if (event.paymentStatus === 'PAID') {
const [accountId] = event.merchantReferenceDisplay.split(':');
@@ -101,11 +121,13 @@ export class MontonioWebhookHandlerService
return params.onCheckoutSessionCompleted(order);
}
if (event.paymentStatus === 'FAILED' || event.paymentStatus === 'CANCELLED') {
if (
event.paymentStatus === 'FAILED' ||
event.paymentStatus === 'CANCELLED'
) {
return params.onPaymentFailed(event.uuid);
}
return;
}
}

View File

@@ -2,4 +2,4 @@
This package is responsible for handling all billing related operations using Stripe.
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/billing/stripe).
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/billing/stripe).

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,12 +4,9 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"start": "docker run --rm -it --name=stripe -v ~/.config/stripe:/root/.config/stripe stripe/stripe-cli:latest listen --forward-to http://host.docker.internal:3000/api/billing/webhook"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./components": "./src/components/index.ts"
@@ -21,8 +18,6 @@
},
"devDependencies": {
"@kit/billing": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",

View File

@@ -1,3 +1,3 @@
# CMS - @kit/cms
CMS abstraction layer for the Makerkit framework.
CMS abstraction layer for the Makerkit framework.

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,19 +4,14 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/cms-types": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/keystatic": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/wordpress": "workspace:*",

View File

@@ -2,4 +2,4 @@
Implementation of the CMS layer using the Keystatic library.
Please refer to the [Documentation](https://makerkit.dev/docs/next-supabase-turbo/content/keystatic).
Please refer to the [Documentation](https://makerkit.dev/docs/next-supabase-turbo/content/keystatic).

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,11 +4,8 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./renderer": "./src/content-renderer.tsx",
@@ -22,8 +19,6 @@
},
"devDependencies": {
"@kit/cms-types": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@types/node": "^22.15.18",

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,17 +4,12 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*"
},
"typesVersions": {

View File

@@ -1,3 +1,3 @@
# CMS/Wordpress - @kit/wordpress
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/content/wordpress).
Please refer to the [documentation](https://makerkit.dev/docs/next-supabase-turbo/content/wordpress).

View File

@@ -31,18 +31,18 @@ services:
- WORDPRESS_DB_NAME=wordpress
- WORDPRESS_DEBUG=1
- WORDPRESS_CONFIG_EXTRA = |
define('FS_METHOD', 'direct');
/** disable wp core auto update */
define('WP_AUTO_UPDATE_CORE', false);
/** local environment settings */
define('WP_CACHE', false);
define('ENVIRONMENT', 'local');
/** force site home url */
if(!defined('WP_HOME')) {
define('WP_HOME', 'http://localhost');
define('WP_SITEURL', WP_HOME);
}
define('FS_METHOD', 'direct');
/** disable wp core auto update */
define('WP_AUTO_UPDATE_CORE', false);
/** local environment settings */
define('WP_CACHE', false);
define('ENVIRONMENT', 'local');
/** force site home url */
if(!defined('WP_HOME')) {
define('WP_HOME', 'http://localhost');
define('WP_SITEURL', WP_HOME);
}
volumes:
db_data:
db_data:

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,20 +4,15 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"start": "docker compose up"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
"./renderer": "./src/content-renderer.tsx"
},
"devDependencies": {
"@kit/cms-types": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@types/node": "^22.15.18",

View File

@@ -3,6 +3,7 @@
This package is responsible for handling webhooks from database changes.
For example:
1. when an account is deleted, we handle the cleanup of all related data in the third-party services.
2. when a user is invited, we send an email to the user.
3. when an account member is added, we update the subscription in the third-party services
@@ -21,4 +22,4 @@ WEBHOOK_SENDER_PROVIDER=svix
For example, you can add [https://docs.svix.com/quickstart]](Swix) as a webhook sender provider that receives webhooks from the database changes and forwards them to your application.
Svix is not implemented yet.
Svix is not implemented yet.

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,19 +4,14 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/billing": "workspace:*",
"@kit/billing-gateway": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/stripe": "workspace:*",
"@kit/montonio": "workspace:*",

View File

@@ -6,7 +6,7 @@ const webhooksSecret = z
.string({
error: `Provide the variable SUPABASE_DB_WEBHOOK_SECRET. This is used to authenticate the webhook event from Supabase.`,
})
.describe(`The secret used to verify the webhook signature`,)
.describe(`The secret used to verify the webhook signature`)
.min(1)
.parse(process.env.SUPABASE_DB_WEBHOOK_SECRET);

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,11 +4,8 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
@@ -16,9 +13,7 @@
"@react-email/components": "0.0.41"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/i18n": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*"
},
"typesVersions": {

View File

@@ -1,6 +1,7 @@
import { TFunction } from "i18next";
import { Text } from "@react-email/components";
import { EmailFooter } from "./footer";
import { Text } from '@react-email/components';
import { TFunction } from 'i18next';
import { EmailFooter } from './footer';
export default function CommonFooter({ t }: { t: TFunction }) {
const namespace = 'common';
@@ -15,8 +16,12 @@ export default function CommonFooter({ t }: { t: TFunction }) {
return (
<EmailFooter>
{lines.map((line, index) => (
<Text key={index} className="text-[16px] leading-[24px] text-[#242424]" dangerouslySetInnerHTML={{ __html: line }} />
<Text
key={index}
className="text-[16px] leading-[24px] text-[#242424]"
dangerouslySetInnerHTML={{ __html: line }}
/>
))}
</EmailFooter>
)
);
}

View File

@@ -65,7 +65,7 @@ export async function renderPatientFirstResultsReceivedEmail({
</Text>
<Text>{t(`${namespace}:p2`)}</Text>
<Text>{t(`${namespace}:p3`)}</Text>
<Text>{t(`${namespace}:p4`)}</Text>
<Text>{t(`${namespace}:p4`)}</Text>
<CommonFooter t={t} />
</EmailContent>
</EmailWrapper>

View File

@@ -6,4 +6,4 @@
"paragraph2": "We're sorry to see you go. Please note that this action is irreversible, and we'll make sure to delete all of your data from our systems.",
"paragraph3": "We thank you again for using {{productName}}.",
"paragraph4": "The {{productName}} Team"
}
}

View File

@@ -1,8 +1,8 @@
{
"previewText": "All analysis results have been received",
"subject": "All patient analysis results have been received",
"openOrdersHeading": "Review the results and prepare a summary:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello"
}
"previewText": "All analysis results have been received",
"subject": "All patient analysis results have been received",
"openOrdersHeading": "Review the results and prepare a summary:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello"
}

View File

@@ -5,4 +5,4 @@
"lines3": "Customer service: <a href=\"tel:+37258871517\">+372 5887 1517</a>",
"lines4": "<a href=\"https://www.medreport.ee\">www.medreport.ee</a>"
}
}
}

View File

@@ -5,4 +5,4 @@
"contactPerson": "Contact Person:",
"email": "Email:",
"phone": "Phone:"
}
}

View File

@@ -1,8 +1,8 @@
{
"subject": "Doctor's summary has arrived",
"previewText": "The doctor has prepared a summary of the test results.",
"p1": "The doctor's summary has arrived:",
"p2": "It is recommended to have a comprehensive health check-up regularly, at least once a year, if you wish to maintain an active and fulfilling lifestyle.",
"p3": "MedReport makes it easy, convenient, and fast to view health data in one place and order health check-ups.",
"p4": "SYNLAB customer support phone: 17123"
"subject": "Doctor's summary has arrived",
"previewText": "The doctor has prepared a summary of the test results.",
"p1": "The doctor's summary has arrived:",
"p2": "It is recommended to have a comprehensive health check-up regularly, at least once a year, if you wish to maintain an active and fulfilling lifestyle.",
"p3": "MedReport makes it easy, convenient, and fast to view health data in one place and order health check-ups.",
"p4": "SYNLAB customer support phone: 17123"
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "First analysis responses received",
"subject": "New job - first analysis responses received",
"resultsReceivedForOrders": "New job available to claim",
"openOrdersHeading": "See here:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}
"previewText": "First analysis responses received",
"subject": "New job - first analysis responses received",
"resultsReceivedForOrders": "New job available to claim",
"openOrdersHeading": "See here:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}

View File

@@ -6,4 +6,4 @@
"joinTeam": "Join {{teamName}}",
"copyPasteLink": "or copy and paste this URL into your browser:",
"invitationIntendedFor": "This invitation is intended for {{invitedUserEmail}}."
}
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "New jobs available",
"subject": "Please write a summary",
"resultsReceivedForOrders": "Please review the results and write a summary.",
"openOrdersHeading": "See here:",
"linkText": "Open job {{nr}}",
"ifLinksDisabled": "If the links do not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}
"previewText": "New jobs available",
"subject": "Please write a summary",
"resultsReceivedForOrders": "Please review the results and write a summary.",
"openOrdersHeading": "See here:",
"linkText": "Open job {{nr}}",
"ifLinksDisabled": "If the links do not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}

View File

@@ -1,13 +1,13 @@
{
"subject": "The referral has been sent to the laboratory. Please go to give samples.",
"heading": "Thank you for your order!",
"previewText": "The referral for tests has been sent to the laboratory.",
"p1": "The referral for tests has been sent to the laboratory digitally. Please go to give samples: {{partnerLocation}}.",
"p2": "If you are unable to go to the selected location to give samples, you may visit any other sampling point convenient for you - <a href='https://medreport.ee/et/verevotupunktid'>see locations and opening hours</a>.",
"p3": "It is recommended to give samples preferably in the morning (before 12:00) and on an empty stomach without drinking or eating (you may drink water).",
"p4": "At the sampling point, please choose in the queue system: under <strong>referrals</strong> select <strong>specialist referral</strong>.",
"p5": "If you have any additional questions, please do not hesitate to contact us.",
"p6": "SYNLAB customer support phone: 17123",
"p1Urine": "The tests include a <strong>urine test</strong>. For the urine test, please collect the first morning urine.",
"p2Urine": "You can buy a sample container at the pharmacy and bring the sample with you (procedure performed at home), or ask for one at the sampling point (procedure performed in the points restroom)."
"subject": "The referral has been sent to the laboratory. Please go to give samples.",
"heading": "Thank you for your order!",
"previewText": "The referral for tests has been sent to the laboratory.",
"p1": "The referral for tests has been sent to the laboratory digitally. Please go to give samples: {{partnerLocation}}.",
"p2": "If you are unable to go to the selected location to give samples, you may visit any other sampling point convenient for you - <a href='https://medreport.ee/et/verevotupunktid'>see locations and opening hours</a>.",
"p3": "It is recommended to give samples preferably in the morning (before 12:00) and on an empty stomach without drinking or eating (you may drink water).",
"p4": "At the sampling point, please choose in the queue system: under <strong>referrals</strong> select <strong>specialist referral</strong>.",
"p5": "If you have any additional questions, please do not hesitate to contact us.",
"p6": "SYNLAB customer support phone: 17123",
"p1Urine": "The tests include a <strong>urine test</strong>. For the urine test, please collect the first morning urine.",
"p2Urine": "You can buy a sample container at the pharmacy and bring the sample with you (procedure performed at home), or ask for one at the sampling point (procedure performed in the points restroom)."
}

View File

@@ -1,8 +1,8 @@
{
"subject": "The first ordered test results have arrived",
"previewText": "The first test results have arrived.",
"p1": "The first test results have arrived:",
"p2": "We will send the next notification once all test results have been received in the system.",
"p3": "If you have any additional questions, please feel free to contact us.",
"p4": "SYNLAB customer support phone: 17123"
"subject": "The first ordered test results have arrived",
"previewText": "The first test results have arrived.",
"p1": "The first test results have arrived:",
"p2": "We will send the next notification once all test results have been received in the system.",
"p3": "If you have any additional questions, please feel free to contact us.",
"p4": "SYNLAB customer support phone: 17123"
}

View File

@@ -1,7 +1,7 @@
{
"subject": "All ordered test results have arrived. Awaiting doctor's summary.",
"previewText": "All test results have arrived.",
"p1": "All test results have arrived:",
"p2": "We will send the next notification once the doctor's summary has been prepared.",
"p3": "SYNLAB customer support phone: 17123"
"subject": "All ordered test results have arrived. Awaiting doctor's summary.",
"previewText": "All test results have arrived.",
"p1": "All test results have arrived:",
"p2": "We will send the next notification once the doctor's summary has been prepared.",
"p3": "SYNLAB customer support phone: 17123"
}

View File

@@ -1,12 +1,12 @@
{
"subject": "Your Medreport order has been placed - {{analysisPackageName}}",
"previewText": "Your Medreport order has been placed - {{analysisPackageName}}",
"heading": "Your Medreport order has been placed - {{analysisPackageName}}",
"hello": "Hello {{personName}},",
"lines1": "The order for {{analysisPackageName}} analysis package has been sent to the lab. Please go to the lab to collect the sample: Synlab - {{partnerLocationName}}",
"lines2": "<i>If you are unable to go to the lab to collect the sample, you can go to any other suitable collection point - <a href=\"https://medreport.ee/et/verevotupunktid\">view locations and opening hours</a>.</i>",
"lines3": "It is recommended to collect the sample in the morning (before 12:00) and not to eat or drink (water can be drunk).",
"lines4": "At the collection point, select the order from the queue: the order from the doctor.",
"lines5": "If you have any questions, please contact us.",
"lines6": "SYNLAB customer service phone: <a href=\"tel:+37217123\">17123</a>"
}
"subject": "Your Medreport order has been placed - {{analysisPackageName}}",
"previewText": "Your Medreport order has been placed - {{analysisPackageName}}",
"heading": "Your Medreport order has been placed - {{analysisPackageName}}",
"hello": "Hello {{personName}},",
"lines1": "The order for {{analysisPackageName}} analysis package has been sent to the lab. Please go to the lab to collect the sample: Synlab - {{partnerLocationName}}",
"lines2": "<i>If you are unable to go to the lab to collect the sample, you can go to any other suitable collection point - <a href=\"https://medreport.ee/et/verevotupunktid\">view locations and opening hours</a>.</i>",
"lines3": "It is recommended to collect the sample in the morning (before 12:00) and not to eat or drink (water can be drunk).",
"lines4": "At the collection point, select the order from the queue: the order from the doctor.",
"lines5": "If you have any questions, please contact us.",
"lines6": "SYNLAB customer service phone: <a href=\"tel:+37217123\">17123</a>"
}

View File

@@ -1,8 +1,8 @@
{
"previewText": "Kõik analüüside vastused on saabunud",
"subject": "Patsiendi kõikide analüüside vastused on saabunud",
"openOrdersHeading": "Vaata tulemusi ja kirjuta kokkuvõte:",
"linkText": "Vaata tulemusi",
"ifLinksDisabled": "Kui link ei tööta, näed analüüsitulemusi sellelt aadressilt:",
"hello": "Tere"
}
"previewText": "Kõik analüüside vastused on saabunud",
"subject": "Patsiendi kõikide analüüside vastused on saabunud",
"openOrdersHeading": "Vaata tulemusi ja kirjuta kokkuvõte:",
"linkText": "Vaata tulemusi",
"ifLinksDisabled": "Kui link ei tööta, näed analüüsitulemusi sellelt aadressilt:",
"hello": "Tere"
}

View File

@@ -7,4 +7,4 @@
},
"helloName": "Tere, {{name}}",
"hello": "Tere"
}
}

View File

@@ -1,8 +1,8 @@
{
"subject": "Arsti kokkuvõte on saabunud",
"previewText": "Arst on koostanud kokkuvõte analüüsitulemustele.",
"p1": "Arsti kokkuvõte on saabunud:",
"p2": "Põhjalikul terviseuuringul on soovituslik käia regulaarselt, aga vähemalt üks kord aastas, kui soovite säilitada aktiivset ja täisväärtuslikku elustiili.",
"p3": "MedReport aitab lihtsalt, mugavalt ja kiirelt terviseandmeid ühest kohast vaadata ning tellida terviseuuringuid.",
"p4": "SYNLAB klienditoe telefon: 17123"
}
"subject": "Arsti kokkuvõte on saabunud",
"previewText": "Arst on koostanud kokkuvõte analüüsitulemustele.",
"p1": "Arsti kokkuvõte on saabunud:",
"p2": "Põhjalikul terviseuuringul on soovituslik käia regulaarselt, aga vähemalt üks kord aastas, kui soovite säilitada aktiivset ja täisväärtuslikku elustiili.",
"p3": "MedReport aitab lihtsalt, mugavalt ja kiirelt terviseandmeid ühest kohast vaadata ning tellida terviseuuringuid.",
"p4": "SYNLAB klienditoe telefon: 17123"
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "Saabusid esimesed analüüside vastused",
"subject": "Uus töö - saabusid esimesed analüüside vastused",
"resultsReceivedForOrders": "Patsiendile saabusid esimesed analüüside vastused.",
"openOrdersHeading": "Vaata siit:",
"linkText": "Vaata tulemusi",
"ifLinksDisabled": "Kui link ei tööta, näed analüüsitulemusi sellelt aadressilt:",
"hello": "Tere"
}
"previewText": "Saabusid esimesed analüüside vastused",
"subject": "Uus töö - saabusid esimesed analüüside vastused",
"resultsReceivedForOrders": "Patsiendile saabusid esimesed analüüside vastused.",
"openOrdersHeading": "Vaata siit:",
"linkText": "Vaata tulemusi",
"ifLinksDisabled": "Kui link ei tööta, näed analüüsitulemusi sellelt aadressilt:",
"hello": "Tere"
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "Palun koosta kokkuvõte",
"subject": "Palun koosta kokkuvõte",
"resultsReceivedForOrders": "Palun vaata tulemused üle ja kirjuta kokkuvõte.",
"openOrdersHeading": "Vaata siit:",
"linkText": "Töö {{nr}}",
"ifLinksDisabled": "Kui lingid ei tööta, näed vabasid töid sellelt aadressilt:",
"hello": "Tere"
}
"previewText": "Palun koosta kokkuvõte",
"subject": "Palun koosta kokkuvõte",
"resultsReceivedForOrders": "Palun vaata tulemused üle ja kirjuta kokkuvõte.",
"openOrdersHeading": "Vaata siit:",
"linkText": "Töö {{nr}}",
"ifLinksDisabled": "Kui lingid ei tööta, näed vabasid töid sellelt aadressilt:",
"hello": "Tere"
}

View File

@@ -1,13 +1,13 @@
{
"subject": "Saatekiri on saadetud laborisse. Palun mine proove andma.",
"heading": "Täname tellimuse eest!",
"previewText": "Saatekiri uuringute tegemiseks on saadetud laborisse.",
"p1": "Saatekiri uuringute tegemiseks on saadetud laborisse digitaalselt. Palun mine proove andma: {{partnerLocation}}.",
"p2": "Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - <a href='https://medreport.ee/et/verevotupunktid'>vaata asukohti ja lahtiolekuaegasid</a>.",
"p3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).",
"p4": "Proovivõtupunktis valige järjekorrasüsteemis: <strong>saatekirjad</strong> alt <strong>eriarsti saatekiri</strong>",
"p5": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"p6": "SYNLAB klienditoe telefon: 17123",
"p1Urine": "Analüüsides on ette nähtud <strong>uriinianalüüs</strong>. Uriinianalüüsiks võta hommikune esmane uriin.",
"p2Urine": "Proovitopsi võib soetada apteegist ja analüüsi kaasa võtta (teostada protseduur kodus) või küsida proovivõtupunktist (teostada protseduur proovipunkti wc-s)."
}
"subject": "Saatekiri on saadetud laborisse. Palun mine proove andma.",
"heading": "Täname tellimuse eest!",
"previewText": "Saatekiri uuringute tegemiseks on saadetud laborisse.",
"p1": "Saatekiri uuringute tegemiseks on saadetud laborisse digitaalselt. Palun mine proove andma: {{partnerLocation}}.",
"p2": "Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - <a href='https://medreport.ee/et/verevotupunktid'>vaata asukohti ja lahtiolekuaegasid</a>.",
"p3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).",
"p4": "Proovivõtupunktis valige järjekorrasüsteemis: <strong>saatekirjad</strong> alt <strong>eriarsti saatekiri</strong>",
"p5": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"p6": "SYNLAB klienditoe telefon: 17123",
"p1Urine": "Analüüsides on ette nähtud <strong>uriinianalüüs</strong>. Uriinianalüüsiks võta hommikune esmane uriin.",
"p2Urine": "Proovitopsi võib soetada apteegist ja analüüsi kaasa võtta (teostada protseduur kodus) või küsida proovivõtupunktist (teostada protseduur proovipunkti wc-s)."
}

View File

@@ -1,8 +1,8 @@
{
"subject": "Saabusid tellitud uuringute esimesed tulemused",
"previewText": "Esimesed uuringute tulemused on saabunud.",
"p1": "Esimesed uuringute tulemused on saabunud:",
"p2": "Saadame järgmise teavituse, kui kõik uuringute vastused on saabunud süsteemi.",
"p3": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"p4": "SYNLAB klienditoe telefon: 17123"
}
"subject": "Saabusid tellitud uuringute esimesed tulemused",
"previewText": "Esimesed uuringute tulemused on saabunud.",
"p1": "Esimesed uuringute tulemused on saabunud:",
"p2": "Saadame järgmise teavituse, kui kõik uuringute vastused on saabunud süsteemi.",
"p3": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"p4": "SYNLAB klienditoe telefon: 17123"
}

View File

@@ -1,7 +1,7 @@
{
"subject": "Kõikide tellitud uuringute tulemused on saabunud. Ootab arsti kokkuvõtet.",
"previewText": "Kõikide uuringute tulemused on saabunud.",
"p1": "Kõikide uuringute tulemused on saabunud:",
"p2": "Saadame järgmise teavituse kui arsti kokkuvõte on koostatud.",
"p3": "SYNLAB klienditoe telefon: 17123"
}
"subject": "Kõikide tellitud uuringute tulemused on saabunud. Ootab arsti kokkuvõtet.",
"previewText": "Kõikide uuringute tulemused on saabunud.",
"p1": "Kõikide uuringute tulemused on saabunud:",
"p2": "Saadame järgmise teavituse kui arsti kokkuvõte on koostatud.",
"p3": "SYNLAB klienditoe telefon: 17123"
}

View File

@@ -1,12 +1,12 @@
{
"subject": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"previewText": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"heading": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"hello": "Tere {{personName}},",
"lines1": "Saatekiri {{analysisPackageName}} analüüsi uuringuteks on saadetud laborisse digitaalselt. Palun mine proove andma: Synlab - {{partnerLocationName}}",
"lines2": "<i>Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - <a href=\"https://medreport.ee/et/verevotupunktid\">vaata asukohti ja lahtiolekuaegasid</a>.</i>",
"lines3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).",
"lines4": "Proovivõtupunktis valige järjekorrasüsteemis: <strong>saatekirjad</strong> alt <strong>eriarsti saatekiri</strong>.",
"lines5": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"lines6": "SYNLAB klienditoe telefon: <a href=\"tel:+37217123\">17123</a>"
}
"subject": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"previewText": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"heading": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}",
"hello": "Tere {{personName}},",
"lines1": "Saatekiri {{analysisPackageName}} analüüsi uuringuteks on saadetud laborisse digitaalselt. Palun mine proove andma: Synlab - {{partnerLocationName}}",
"lines2": "<i>Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - <a href=\"https://medreport.ee/et/verevotupunktid\">vaata asukohti ja lahtiolekuaegasid</a>.</i>",
"lines3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).",
"lines4": "Proovivõtupunktis valige järjekorrasüsteemis: <strong>saatekirjad</strong> alt <strong>eriarsti saatekiri</strong>.",
"lines5": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
"lines6": "SYNLAB klienditoe telefon: <a href=\"tel:+37217123\">17123</a>"
}

View File

@@ -1,8 +1,8 @@
{
"previewText": "All analysis results have been received",
"subject": "All patient analysis results have been received",
"openOrdersHeading": "Review the results and prepare a summary:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello"
}
"previewText": "All analysis results have been received",
"subject": "All patient analysis results have been received",
"openOrdersHeading": "Review the results and prepare a summary:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello"
}

View File

@@ -5,4 +5,4 @@
"lines3": "Служба поддержки: <a href=\"tel:+37258871517\">+372 5887 1517</a>",
"lines4": "<a href=\"https://www.medreport.ee\">www.medreport.ee</a>"
}
}
}

View File

@@ -5,4 +5,4 @@
"contactPerson": "Контактное лицо:",
"email": "Электронная почта:",
"phone": "Телефон:"
}
}

View File

@@ -1,8 +1,8 @@
{
"subject": "Заключение врача готово",
"previewText": "Врач подготовил заключение по результатам анализов.",
"p1": "Заключение врача готово:",
"p2": "Рекомендуется проходить комплексное обследование регулярно, но как минимум один раз в год, если вы хотите сохранить активный и полноценный образ жизни.",
"p3": "MedReport позволяет легко, удобно и быстро просматривать медицинские данные в одном месте и заказывать обследования.",
"p4": "Телефон службы поддержки SYNLAB: 17123"
}
"subject": "Заключение врача готово",
"previewText": "Врач подготовил заключение по результатам анализов.",
"p1": "Заключение врача готово:",
"p2": "Рекомендуется проходить комплексное обследование регулярно, но как минимум один раз в год, если вы хотите сохранить активный и полноценный образ жизни.",
"p3": "MedReport позволяет легко, удобно и быстро просматривать медицинские данные в одном месте и заказывать обследования.",
"p4": "Телефон службы поддержки SYNLAB: 17123"
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "First analysis responses received",
"subject": "New job - first analysis responses received",
"resultsReceivedForOrders": "New job available to claim",
"openOrdersHeading": "See here:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello,"
}
"previewText": "First analysis responses received",
"subject": "New job - first analysis responses received",
"resultsReceivedForOrders": "New job available to claim",
"openOrdersHeading": "See here:",
"linkText": "See results",
"ifLinksDisabled": "If the link does not work, you can see the results by copying this link into your browser.",
"hello": "Hello,"
}

View File

@@ -1,9 +1,9 @@
{
"previewText": "New jobs available",
"subject": "Please write a summary",
"resultsReceivedForOrders": "Please review the results and write a summary.",
"openOrdersHeading": "See here:",
"linkText": "Open job {{nr}}",
"ifLinksDisabled": "If the links do not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}
"previewText": "New jobs available",
"subject": "Please write a summary",
"resultsReceivedForOrders": "Please review the results and write a summary.",
"openOrdersHeading": "See here:",
"linkText": "Open job {{nr}}",
"ifLinksDisabled": "If the links do not work, you can see available jobs by copying this link into your browser.",
"hello": "Hello,"
}

View File

@@ -1,13 +1,13 @@
{
"subject": "Направление отправлено в лабораторию. Пожалуйста, сдайте анализы.",
"heading": "Спасибо за заказ!",
"previewText": "Направление на обследование отправлено в лабораторию.",
"p1": "Направление на обследование было отправлено в лабораторию в цифровом виде. Пожалуйста, сдайте анализы: {{partnerLocation}}.",
"p2": "Если у вас нет возможности прийти в выбранный пункт сдачи анализов, вы можете обратиться в любой удобный для вас пункт <a href='https://medreport.ee/et/verevotupunktid'>посмотреть адреса и часы работы</a>.",
"p3": "Рекомендуется сдавать анализы утром (до 12:00) натощак, без еды и напитков (разрешается пить воду).",
"p4": "В пункте сдачи анализов выберите в системе очереди: в разделе <strong>направления</strong> → <strong>направление от специалиста</strong>.",
"p5": "Если у вас возникли дополнительные вопросы, пожалуйста, свяжитесь с нами.",
"p6": "Телефон службы поддержки SYNLAB: 17123",
"p1Urine": "В обследование входит <strong>анализ мочи</strong>. Для анализа необходимо собрать первую утреннюю мочу.",
"p2Urine": "Контейнер можно приобрести в аптеке и принести образец с собой (процедура проводится дома) или взять контейнер в пункте сдачи (процедура проводится в туалете пункта)."
"subject": "Направление отправлено в лабораторию. Пожалуйста, сдайте анализы.",
"heading": "Спасибо за заказ!",
"previewText": "Направление на обследование отправлено в лабораторию.",
"p1": "Направление на обследование было отправлено в лабораторию в цифровом виде. Пожалуйста, сдайте анализы: {{partnerLocation}}.",
"p2": "Если у вас нет возможности прийти в выбранный пункт сдачи анализов, вы можете обратиться в любой удобный для вас пункт <a href='https://medreport.ee/et/verevotupunktid'>посмотреть адреса и часы работы</a>.",
"p3": "Рекомендуется сдавать анализы утром (до 12:00) натощак, без еды и напитков (разрешается пить воду).",
"p4": "В пункте сдачи анализов выберите в системе очереди: в разделе <strong>направления</strong> → <strong>направление от специалиста</strong>.",
"p5": "Если у вас возникли дополнительные вопросы, пожалуйста, свяжитесь с нами.",
"p6": "Телефон службы поддержки SYNLAB: 17123",
"p1Urine": "В обследование входит <strong>анализ мочи</strong>. Для анализа необходимо собрать первую утреннюю мочу.",
"p2Urine": "Контейнер можно приобрести в аптеке и принести образец с собой (процедура проводится дома) или взять контейнер в пункте сдачи (процедура проводится в туалете пункта)."
}

View File

@@ -1,8 +1,8 @@
{
"subject": "Поступили первые результаты заказанных исследований",
"previewText": "Первые результаты исследований поступили.",
"p1": "Первые результаты исследований поступили:",
"p2": "Мы отправим следующее уведомление, когда все результаты исследований будут получены в системе.",
"p3": "Если у вас возникнут дополнительные вопросы, пожалуйста, свяжитесь с нами.",
"p4": "Телефон службы поддержки SYNLAB: 17123"
"subject": "Поступили первые результаты заказанных исследований",
"previewText": "Первые результаты исследований поступили.",
"p1": "Первые результаты исследований поступили:",
"p2": "Мы отправим следующее уведомление, когда все результаты исследований будут получены в системе.",
"p3": "Если у вас возникнут дополнительные вопросы, пожалуйста, свяжитесь с нами.",
"p4": "Телефон службы поддержки SYNLAB: 17123"
}

View File

@@ -1,7 +1,7 @@
{
"subject": "Все заказанные результаты исследований поступили. Ожидается заключение врача.",
"previewText": "Все результаты исследований поступили.",
"p1": "Все результаты исследований поступили:",
"p2": "Мы отправим следующее уведомление, когда заключение врача будет подготовлено.",
"p3": "Телефон службы поддержки SYNLAB: 17123"
}
"subject": "Все заказанные результаты исследований поступили. Ожидается заключение врача.",
"previewText": "Все результаты исследований поступили.",
"p1": "Все результаты исследований поступили:",
"p2": "Мы отправим следующее уведомление, когда заключение врача будет подготовлено.",
"p3": "Телефон службы поддержки SYNLAB: 17123"
}

View File

@@ -1,12 +1,12 @@
{
"subject": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"previewText": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"heading": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"hello": "Здравствуйте, {{personName}},",
"lines1": "Направление на исследование {{analysisPackageName}} было отправлено в лабораторию в цифровом виде. Пожалуйста, сдайте анализы: Synlab - {{partnerLocationName}}",
"lines2": "<i>Если вы не можете посетить выбранный пункт сдачи анализов, вы можете обратиться в удобный для вас пункт - <a href=\"https://medreport.ee/et/verevotupunktid\">посмотреть адреса и часы работы</a>.</i>",
"lines3": "Рекомендуется сдавать анализы утром (до 12:00) натощак (можно пить воду).",
"lines4": "В пункте сдачи анализов выберите в системе очереди: <strong>направления</strong> -> <strong>направление от специалиста</strong>.",
"lines5": "Если у вас возникнут дополнительные вопросы, смело свяжитесь с нами.",
"lines6": "Телефон службы поддержки SYNLAB: <a href=\"tel:+37217123\">17123</a>"
}
"subject": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"previewText": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"heading": "Ваш заказ Medreport подтвержден - {{analysisPackageName}}",
"hello": "Здравствуйте, {{personName}},",
"lines1": "Направление на исследование {{analysisPackageName}} было отправлено в лабораторию в цифровом виде. Пожалуйста, сдайте анализы: Synlab - {{partnerLocationName}}",
"lines2": "<i>Если вы не можете посетить выбранный пункт сдачи анализов, вы можете обратиться в удобный для вас пункт - <a href=\"https://medreport.ee/et/verevotupunktid\">посмотреть адреса и часы работы</a>.</i>",
"lines3": "Рекомендуется сдавать анализы утром (до 12:00) натощак (можно пить воду).",
"lines4": "В пункте сдачи анализов выберите в системе очереди: <strong>направления</strong> -> <strong>направление от специалиста</strong>.",
"lines5": "Если у вас возникнут дополнительные вопросы, смело свяжитесь с нами.",
"lines6": "Телефон службы поддержки SYNLAB: <a href=\"tel:+37217123\">17123</a>"
}

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,8 +4,6 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"exports": {
@@ -24,12 +22,10 @@
"@hookform/resolvers": "^5.0.1",
"@kit/billing-gateway": "workspace:*",
"@kit/email-templates": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/mailers": "workspace:*",
"@kit/monitoring": "workspace:*",
"@kit/next": "workspace:*",
"@kit/otp": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
@@ -46,7 +42,6 @@
"react-dom": "19.1.0",
"sonner": "^2.0.3"
},
"prettier": "@kit/prettier-config",
"typesVersions": {
"*": {
"*": [

View File

@@ -112,7 +112,7 @@ export function AccountSelector({
role="combobox"
aria-expanded={open}
className={cn(
'dark:shadow-primary/10 group w-full min-w-0 px-4 py-2 h-10 border-1 lg:w-auto lg:max-w-fit',
'dark:shadow-primary/10 group h-10 w-full min-w-0 border-1 px-4 py-2 lg:w-auto lg:max-w-fit',
{
'justify-start': !collapsed,
'm-auto justify-center px-4 lg:w-full': collapsed,
@@ -124,7 +124,7 @@ export function AccountSelector({
condition={selected}
fallback={
<span
className={cn('flex max-w-full items-center size-4', {
className={cn('flex size-4 max-w-full items-center', {
'justify-center gap-x-0': collapsed,
'gap-x-4': !collapsed,
})}
@@ -148,7 +148,7 @@ export function AccountSelector({
'gap-x-4': !collapsed,
})}
>
<Avatar className={'rounded-md size-6'}>
<Avatar className={'size-6 rounded-md'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback
@@ -233,7 +233,7 @@ export function AccountSelector({
}}
>
<div className={'flex items-center'}>
<Avatar className={'rounded-xs mr-2 h-6 w-6'}>
<Avatar className={'mr-2 h-6 w-6 rounded-xs'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback
@@ -297,7 +297,7 @@ export function AccountSelector({
function UserAvatar(props: { pictureUrl?: string }) {
return (
<Avatar className={'rounded-md size-6'}>
<Avatar className={'size-6 rounded-md'}>
<AvatarImage src={props.pictureUrl} />
</Avatar>
);

View File

@@ -1,3 +1,3 @@
export * from './user-workspace-context';
export * from './personal-account-settings/mfa/multi-factor-auth-list'
export * from './personal-account-settings/mfa/multi-factor-auth-setup-dialog'
export * from './personal-account-settings/mfa/multi-factor-auth-list';
export * from './personal-account-settings/mfa/multi-factor-auth-setup-dialog';

View File

@@ -81,7 +81,8 @@ export function PersonalAccountDropdown({
const { name, last_name } = personalAccountData ?? {};
const firstNameLabel = toTitleCase(name) ?? '-';
const fullNameLabel = name && last_name ? toTitleCase(`${name} ${last_name}`) : '-';
const fullNameLabel =
name && last_name ? toTitleCase(`${name} ${last_name}`) : '-';
const hasTotpFactor = useMemo(() => {
const factors = user?.factors ?? [];

View File

@@ -347,9 +347,7 @@ function FactorQrCode({
<QrImage src={form.getValues('qrCode')} />
</div>
<p className='text-center text-sm'>
{form.getValues('totpSecret')}
</p>
<p className="text-center text-sm">{form.getValues('totpSecret')}</p>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database';
import PersonalCode from '~/lib/utils';
import { AccountWithParams } from '../types/accounts';
@@ -11,7 +12,7 @@ import { AccountWithParams } from '../types/accounts';
* @param {SupabaseClient<Database>} client - The Supabase client instance.
*/
class AccountsApi {
constructor(private readonly client: SupabaseClient<Database>) { }
constructor(private readonly client: SupabaseClient<Database>) {}
/**
* @name getAccount

View File

@@ -11,13 +11,13 @@ export enum ApplicationRoleEnum {
export type AccountWithParams =
Database['medreport']['Tables']['accounts']['Row'] & {
accountParams:
| (Pick<
Database['medreport']['Tables']['account_params']['Row'],
'weight' | 'height'
> & {
isSmoker:
| Database['medreport']['Tables']['account_params']['Row']['is_smoker']
| (Pick<
Database['medreport']['Tables']['account_params']['Row'],
'weight' | 'height'
> & {
isSmoker:
| Database['medreport']['Tables']['account_params']['Row']['is_smoker']
| null;
})
| null;
})
| null;
};

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,16 +4,11 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@kit/eslint-config": "workspace:*",
"@kit/next": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",

View File

@@ -48,7 +48,7 @@ export function AdminCreateUserDialog(props: React.PropsWithChildren) {
email: '',
password: '',
emailConfirm: false,
personalCode: ''
personalCode: '',
},
mode: 'onBlur',
});
@@ -163,7 +163,7 @@ export function AdminCreateUserDialog(props: React.PropsWithChildren) {
<FormField
name={'emailConfirm'}
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormItem className="flex flex-row items-start space-y-0 space-x-3 rounded-md border p-4">
<FormControl>
<Checkbox
checked={field.value}

View File

@@ -148,12 +148,17 @@ export const deleteAccountAction = adminAction(
}
const medusa = getAdminSdk();
const { customer_groups } = await medusa.admin.customerGroup.list();
const customerGroup = customer_groups.find(({ name }) => name === customerGroupName);
const customerGroup = customer_groups.find(
({ name }) => name === customerGroupName,
);
if (customerGroup) {
try {
await medusa.admin.customerGroup.delete(customerGroup.id);
} catch (e) {
logger.error({ accountId }, `Error deleting Medusa customer group for company ${customerGroupName}`);
logger.error(
{ accountId },
`Error deleting Medusa customer group for company ${customerGroupName}`,
);
throw e;
}
}
@@ -288,22 +293,29 @@ export const createCompanyAccountAction = enhanceAction(
logger.info(ctx, `Creating Medusa customer group`);
const medusa = getAdminSdk();
const { customer_groups: existingCustomerGroups } = await medusa.admin.customerGroup.list();
const isExisting = existingCustomerGroups.find((group) => group.name === name);
const { customer_groups: existingCustomerGroups } =
await medusa.admin.customerGroup.list();
const isExisting = existingCustomerGroups.find(
(group) => group.name === name,
);
if (isExisting) {
logger.info(ctx, `Customer group already exists`);
} else {
logger.info(ctx, `Creating Medusa customer group`);
const { data: account } = await client
.schema('medreport').from('accounts')
.schema('medreport')
.from('accounts')
.select('medusa_account_id')
.eq('personal_code', ownerPersonalCode)
.single().throwOnError();
.single()
.throwOnError();
const medusaAccountId = account.medusa_account_id;
if (!medusaAccountId) {
logger.error(ctx, `User has no Medusa account ID`);
} else {
const { customer_group: { id: customerGroupId } } = await medusa.admin.customerGroup.create({ name });
const {
customer_group: { id: customerGroupId },
} = await medusa.admin.customerGroup.create({ name });
const { customers } = await medusa.admin.customer.list({
id: medusaAccountId,
});
@@ -316,7 +328,6 @@ export const createCompanyAccountAction = enhanceAction(
});
}
}
}
redirect(`/admin/accounts/${data.id}`);

View File

@@ -1,10 +1,13 @@
import { z } from 'zod';
export const CreateUserProfileSchema = z.object({
personalCode: z.string().regex(/^[1-6]\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}\d$/, {
message: 'Invalid Estonian personal code format',
}),
personalCode: z
.string()
.regex(/^[1-6]\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}\d$/, {
message: 'Invalid Estonian personal code format',
}),
});
export type CreateUserProfileSchemaType = z.infer<typeof CreateUserProfileSchema>;
export type CreateUserProfileSchemaType = z.infer<
typeof CreateUserProfileSchema
>;

View File

@@ -1,9 +1,11 @@
import { z } from 'zod';
export const CreateUserSchema = z.object({
personalCode: z.string().regex(/^[1-6]\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}\d$/, {
message: 'Invalid Estonian personal code format',
}),
personalCode: z
.string()
.regex(/^[1-6]\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}\d$/, {
message: 'Invalid Estonian personal code format',
}),
email: z.string().email({ message: 'Please enter a valid email address' }),
password: z
.string()

View File

@@ -44,7 +44,8 @@ class AdminAccountsService {
.from('accounts')
.select('*')
.eq('id', accountId)
.single().throwOnError();
.single()
.throwOnError();
return data;
}

View File

@@ -1,8 +1,9 @@
import Medusa from "@medusajs/js-sdk"
import Medusa from '@medusajs/js-sdk';
export const getAdminSdk = () => {
const medusaBackendUrl = process.env.MEDUSA_BACKEND_PUBLIC_URL!;
const medusaPublishableApiKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!;
const medusaPublishableApiKey =
process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!;
const key = process.env.MEDUSA_SECRET_API_KEY!;
if (!medusaBackendUrl || !medusaPublishableApiKey) {
@@ -13,4 +14,4 @@ export const getAdminSdk = () => {
debug: process.env.NODE_ENV === 'development',
apiKey: key,
});
}
};

View File

@@ -4,7 +4,5 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": [
"node_modules"
]
"exclude": ["node_modules"]
}

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -4,8 +4,6 @@
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"exports": {
@@ -24,8 +22,6 @@
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
@@ -39,7 +35,6 @@
"next": "15.3.2",
"sonner": "^2.0.3"
},
"prettier": "@kit/prettier-config",
"typesVersions": {
"*": {
"*": [

View File

@@ -7,7 +7,7 @@ export function AuthLayoutShell({
return (
<div
className={
'sm:py-auto flex flex-col items-center justify-center py-6 h-screen' +
'sm:py-auto flex h-screen flex-col items-center justify-center py-6' +
' bg-background lg:bg-muted/30 gap-y-10 lg:gap-y-8'
}
>

View File

@@ -4,13 +4,13 @@ import { CheckCircledIcon } from '@radix-ui/react-icons';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
import { Spinner } from '@kit/ui/makerkit/spinner';
import { Trans } from '@kit/ui/trans';
import { useCaptchaToken } from '../captcha/client';
import { usePasswordSignUpFlow } from '../hooks/use-sign-up-flow';
import { AuthErrorAlert } from './auth-error-alert';
import { PasswordSignUpForm } from './password-sign-up-form';
import { Spinner } from '@kit/ui/makerkit/spinner';
interface EmailPasswordSignUpContainerProps {
authConfig: {
@@ -56,8 +56,9 @@ export function EmailPasswordSignUpContainer({
<div className="flex justify-center">
<Spinner />
</div>
) : <SuccessAlert />
}
) : (
<SuccessAlert />
)}
</If>
<If condition={!showVerifyEmailAlert}>

View File

@@ -1,8 +1,9 @@
'use client';
import type { Provider } from '@supabase/supabase-js';
import { useRouter } from 'next/navigation';
import type { Provider } from '@supabase/supabase-js';
import { isBrowser } from '@kit/shared/utils';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
@@ -53,7 +54,7 @@ export function SignUpMethodsContainer(props: {
return;
}
setTimeout(() => {
router.replace(props.paths.updateAccount)
router.replace(props.paths.updateAccount);
}, 2_500);
}}
/>

Some files were not shown because too many files have changed in this diff Show More