Merge branch 'main' into B2B-88

This commit is contained in:
devmc-ee
2025-06-09 10:01:38 +03:00
51 changed files with 3604 additions and 156 deletions

View File

@@ -2,6 +2,7 @@
# https://app.supabase.com/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
MEDIPOST_URL=your-medpost-url
MEDIPOST_USER=your-medpost-user

View File

@@ -9,20 +9,17 @@ import {
import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';
import { MedReportTitle } from '@/components/MedReportTitle';
function Home() {
return (
<div className={'mt-4 flex flex-col space-y-24 py-14'}>
<div className={'container mx-auto'}>
<Hero
title={
<>
<span>Med Report</span>
</>
}
title={<MedReportTitle />}
subtitle={
<span>
Lihtne, mugav ja kiire ülevaade Sinu tervise seisundist
<Trans i18nKey={'marketing:heroSubtitle'} />
</span>
}
cta={<MainCallToActionButton />}
@@ -56,8 +53,8 @@ function MainCallToActionButton() {
</CtaButton>
<CtaButton variant={'link'}>
<Link href={'/contact'}>
<Trans i18nKey={'common:contactUs'} />
<Link href={'/register-company'}>
<Trans i18nKey={'account:createCompanyAccount'} />
</Link>
</CtaButton>
</div>

11
app/(public)/layout.tsx Normal file
View File

@@ -0,0 +1,11 @@
import { withI18n } from '~/lib/i18n/with-i18n';
function SiteLayout(props: React.PropsWithChildren) {
return (
<div className={'flex min-h-[100vh] flex-col justify-center items-center'}>
{props.children}
</div>
);
}
export default withI18n(SiteLayout);

View File

@@ -0,0 +1,90 @@
"use client";
import { MedReportTitle } from "@/components/MedReportTitle";
import React from "react";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import { companySchema } from "@/lib/validations/companySchema";
import { CompanySubmitData } from "@/lib/types/company";
import { submitCompanyRegistration } from "@/lib/services/register-company.service";
import { useRouter } from "next/navigation";
import { Label } from "@kit/ui/label";
import { Input } from "@kit/ui/input";
import { SubmitButton } from "@/components/ui/submit-button";
import { FormItem } from "@kit/ui/form";
import { Trans } from "@kit/ui/trans";
export default function RegisterCompany() {
const router = useRouter();
const {
register,
handleSubmit,
formState: { errors, isValid, isSubmitting },
} = useForm({
resolver: yupResolver(companySchema),
mode: "onChange",
});
async function onSubmit(data: CompanySubmitData) {
const formData = new FormData();
Object.entries(data).forEach(([key, value]) => {
if (value !== undefined) formData.append(key, value);
});
try {
await submitCompanyRegistration(formData);
router.push("/register-company/success");
} catch (err: unknown) {
if (err instanceof Error) {
alert("Server validation error: " + err.message);
}
alert("Server validation error");
}
}
return (
<div className="flex flex-row border rounded-3xl border-border max-w-5xl overflow-hidden">
<div className="flex flex-col text-center py-14 px-12 w-1/2">
<MedReportTitle />
<h1 className="pt-8">Ettevõtte andmed</h1>
<p className="pt-2 text-muted-foreground text-sm">
Pakkumise saamiseks palun sisesta ettevõtte andmed millega MedReport
kasutada kavatsed.
</p>
<form
onSubmit={handleSubmit(onSubmit)}
noValidate
className="flex gap-7 flex-col text-left pt-8 px-6"
>
<FormItem>
<Label>Ettevõtte nimi</Label>
<Input {...register("companyName")} />
</FormItem>
<FormItem>
<Label>Kontaktisik</Label>
<Input {...register("contactPerson")} />
</FormItem>
<FormItem>
<Label>E-mail</Label>
<Input type="email" {...register("email")}></Input>
</FormItem>
<FormItem>
<Label>Telefon</Label>
<Input type="tel" {...register("phone")} />
</FormItem>
<SubmitButton
disabled={!isValid || isSubmitting}
pendingText="Saatmine..."
type="submit"
formAction={submitCompanyRegistration}
className="mt-4 hover:bg-primary/90"
>
<Trans i18nKey={'account:requestCompanyAccount'} />
</SubmitButton>
</form>
</div>
<div className="w-1/2 min-w-[460px] bg-[url(/assets/med-report-logo-big.png)] bg-cover bg-center bg-no-repeat">
</div>
</div>
);
}

View File

@@ -0,0 +1,26 @@
import { MedReportTitle } from "@/components/MedReportTitle";
import { Button } from "@/packages/ui/src/shadcn/button";
import Image from "next/image";
import Link from "next/link";
export default function CompanyRegistrationSuccess() {
return (
<div className="pt-2 px-16 pb-12 border rounded-3xl border-border">
<MedReportTitle />
<div className="flex flex-col items-center px-4">
<Image
src="/assets/success.png"
alt="Success"
className="pt-6 pb-8"
width={326}
height={195}
/>
<h1 className="pb-2">Päring edukalt saadetud!</h1>
<p className=" text-muted-foreground text-sm">Saadame teile esimesel võimalusel vastuse</p>
</div>
<Button className="w-full mt-8">
<Link href="/">Tagasi kodulehele</Link>
</Button>
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { Button } from '@kit/ui/button';
import Link from "next/link";
import React from "react";
export default async function SignIn() {
return (
<div className="flex flex-col gap-2">
<Button variant="outline">
<Link href="/">Smart-ID</Link>
</Button>
<Button variant="outline">
<Link href="/">Mobiil-ID</Link>
</Button>
<Button variant="outline">
<Link href="/">ID-Kaart</Link>
</Button>
<Button variant="outline">
<Link href="/register-company">Loo ettevõtte konto</Link>
</Button>
</div>
);
}

BIN
app/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,10 @@
import { MedReportSmallLogo } from "@/public/assets/MedReportSmallLogo";
export const MedReportTitle = () => (
<div className="flex gap-2 justify-center">
<MedReportSmallLogo />
<span className="text-foreground text-lg font-semibold tracking-tighter">
MedReport
</span>
</div>
);

View File

@@ -0,0 +1,67 @@
import { signOutAction } from "@/lib/actions/sign-out";
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
import Link from "next/link";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { createClient } from "@/utils/supabase/server";
export default async function AuthButton() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!hasEnvVars) {
return (
<>
<div className="flex gap-4 items-center">
<div>
<Badge
variant={"default"}
className="font-normal pointer-events-none"
>
Please update .env.local file with anon key and url
</Badge>
</div>
<div className="flex gap-2">
<Button
asChild
size="sm"
variant={"outline"}
disabled
className="opacity-75 cursor-none pointer-events-none"
>
<Link href="/sign-in">Sign in</Link>
</Button>
<Button
asChild
size="sm"
variant={"default"}
disabled
className="opacity-75 cursor-none pointer-events-none"
>
<Link href="example/sign-up">Sign up</Link>
</Button>
</div>
</div>
</>
);
}
return user ? (
<div className="flex items-center gap-4">
Hey, {user.email}!
<form action={signOutAction}>
<Button type="submit" variant={"outline"}>
Sign out
</Button>
</form>
</div>
) : (
<div className="flex gap-2">
<Button asChild size="sm" variant={"outline"}>
<Link href="/sign-in">Sign in</Link>
</Button>
</div>
);
}

View File

@@ -0,0 +1,23 @@
"use client";
import { Button } from "@kit/ui/button";
import { type ComponentProps } from "react";
import { useFormStatus } from "react-dom";
type Props = ComponentProps<typeof Button> & {
pendingText?: string;
};
export function SubmitButton({
children,
pendingText = "Submitting...",
...props
}: Props) {
const { pending } = useFormStatus();
return (
<Button type="submit" aria-disabled={pending} {...props}>
{pending ? pendingText : children}
</Button>
);
}

Binary file not shown.

Binary file not shown.

10
lib/actions/sign-out.tsx Normal file
View File

@@ -0,0 +1,10 @@
"use server";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export const signOutAction = async () => {
const supabase = await createClient();
await supabase.auth.signOut();
return redirect("/sign-in");
};

View File

@@ -1,4 +1,5 @@
import { Inter as SansFont } from 'next/font/google';
import { Geist as HeadingFont } from 'next/font/google';
import SansFont from 'next/font/local';
import { cn } from '@kit/ui/utils';
@@ -8,18 +9,32 @@ import { cn } from '@kit/ui/utils';
* By default, it uses the Inter font from Google Fonts.
*/
const sans = SansFont({
subsets: ['latin'],
variable: '--font-sans',
fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
preload: true,
weight: ['300', '400', '500', '600', '700'],
src: [
{
path: '../fonts/InterDisplay-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../fonts/InterDisplay-Medium.woff2',
weight: '500',
style: 'medium',
},
],
});
/**
* @heading
* @description Define here the heading font.
*/
const heading = sans;
const heading = HeadingFont({
variable: '--font-heading',
fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial'],
preload: true,
weight: ['300', '400', '500', '600', '700'],
});
// we export these fonts into the root layout
export { sans, heading };

View File

@@ -12,7 +12,7 @@ const defaultLanguage = process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en';
* By default, only the default language is supported.
* Add more languages here if needed.
*/
export const languages: string[] = [defaultLanguage];
export const languages: string[] = [defaultLanguage, 'en', 'ru'];
/**
* The name of the cookie that stores the selected language.

View File

@@ -1,10 +1,16 @@
import {
GetMessageListResponse,
MedipostAction,
MedipostPublicMessageResponse,
Message,
UuringuGrupp,
} from "@/lib/types/medipost";
import { Tables } from "@/supabase/database.types";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import axios from "axios";
import { xml2json } from "xml-js";
import { XMLParser } from "fast-xml-parser";
import { SyncStatus } from "@/lib/types/audit";
import { toArray } from "@/lib/utils";
const BASE_URL = process.env.MEDIPOST_URL!;
const USER = process.env.MEDIPOST_USER!;
@@ -15,9 +21,10 @@ export async function getMessages() {
const publicMessage = await getLatestPublicMessageListItem();
if (!publicMessage) {
return [];
return null;
}
//Teenused tuleb mappida kokku MedReport teenustega. <UuringId> alusel
return getPublicMessage(publicMessage.messageId);
} catch (error) {
console.error(error);
@@ -55,18 +62,13 @@ export async function getPublicMessage(messageId: string) {
Accept: "application/xml",
},
});
const parser = new XMLParser({ ignoreAttributes: false });
const parsed: MedipostPublicMessageResponse = parser.parse(data);
if (data.code && data.code !== 0) {
if (parsed.ANSWER?.CODE && parsed.ANSWER?.CODE !== 0) {
throw new Error(`Failed to get public message (id: ${messageId})`);
}
const parsed = JSON.parse(
xml2json(data, {
compact: true,
spaces: 2,
})
);
return parsed;
}
@@ -124,14 +126,8 @@ export async function getPrivateMessage(messageId: string) {
throw new Error(`Failed to get private message (id: ${messageId})`);
}
const parsed = JSON.parse(
xml2json(data, {
compact: true,
spaces: 2,
})
);
return parsed;
const parser = new XMLParser({ ignoreAttributes: false });
return parser.parse(data);
}
export async function deletePrivateMessage(messageId: string) {
@@ -170,6 +166,187 @@ export async function readPrivateMessageResponse() {
}
}
async function saveAnalysisGroup(
analysisGroup: UuringuGrupp,
supabase: SupabaseClient
) {
const { data: insertedAnalysisGroup, error } = await supabase
.from("analysis_groups")
.upsert(
{
original_id: analysisGroup.UuringuGruppId,
name: analysisGroup.UuringuGruppNimi,
order: analysisGroup.UuringuGruppJarjekord,
},
{ onConflict: "original_id", ignoreDuplicates: false }
)
.select("id");
if (error || !insertedAnalysisGroup[0]?.id) {
throw new Error(
`Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`
);
}
const analysisGroupId = insertedAnalysisGroup[0].id;
const analysisGroupCodes = toArray(analysisGroup.Kood);
const codes: Partial<Tables<"codes">>[] = analysisGroupCodes.map((kood) => ({
hk_code: kood.HkKood,
hk_code_multiplier: kood.HkKoodiKordaja,
coefficient: kood.Koefitsient,
price: kood.Hind,
analysis_group_id: analysisGroupId,
}));
const analysisGroupItems = toArray(analysisGroup.Uuring);
for (const item of analysisGroupItems) {
const analysisElement = item.UuringuElement;
const { data: insertedAnalysisElement, error } = await supabase
.from("analysis_elements")
.upsert(
{
analysis_id_oid: analysisElement.UuringIdOID,
analysis_id_original: analysisElement.UuringId,
tehik_short_loinc: analysisElement.TLyhend,
tehik_loinc_name: analysisElement.KNimetus,
analysis_name_lab: analysisElement.UuringNimi,
order: analysisElement.Jarjekord,
parent_analysis_group_id: analysisGroupId,
material_groups: toArray(item.MaterjalideGrupp),
},
{ onConflict: "analysis_id_original", ignoreDuplicates: false }
)
.select('id');
if (error || !insertedAnalysisElement[0]?.id) {
throw new Error(
`Failed to insert analysis element (id: ${analysisElement.UuringId}), error: ${error?.message}`
);
}
const insertedAnalysisElementId = insertedAnalysisElement[0].id;
if (analysisElement.Kood) {
const analysisElementCodes = toArray(analysisElement.Kood);
codes.push(
...analysisElementCodes.map((kood) => ({
hk_code: kood.HkKood,
hk_code_multiplier: kood.HkKoodiKordaja,
coefficient: kood.Koefitsient,
price: kood.Hind,
analysis_element_id: insertedAnalysisElementId,
}))
);
}
const analyses = analysisElement.UuringuElement;
if (analyses?.length) {
for (const analysis of analyses) {
const { data: insertedAnalysis, error } = await supabase
.from("analyses")
.upsert(
{
analysis_id_oid: analysis.UuringIdOID,
analysis_id_original: analysis.UuringId,
tehik_short_loinc: analysis.TLyhend,
tehik_loinc_name: analysis.KNimetus,
analysis_name_lab: analysis.UuringNimi,
order: analysis.Jarjekord,
parent_analysis_element_id: insertedAnalysisElementId,
},
{ onConflict: "analysis_id_original", ignoreDuplicates: false }
)
.select('id');
if (error || !insertedAnalysis[0]?.id) {
throw new Error(
`Failed to insert analysis (id: ${analysis.UuringId}) error: ${error?.message}`
);
}
const insertedAnalysisId = insertedAnalysis[0].id;
if (analysisElement.Kood) {
const analysisCodes = toArray(analysis.Kood);
codes.push(
...analysisCodes.map((kood) => ({
hk_code: kood.HkKood,
hk_code_multiplier: kood.HkKoodiKordaja,
coefficient: kood.Koefitsient,
price: kood.Hind,
analysis_id: insertedAnalysisId,
}))
);
}
}
}
}
const { error: codesError } = await supabase
.from("codes")
.upsert(codes, { ignoreDuplicates: false });
if (codesError?.code) {
throw new Error(
`Failed to insert codes (analysis group id: ${analysisGroup.UuringuGruppId})`
);
}
}
export async function syncPublicMessage(
message?: MedipostPublicMessageResponse | null
) {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
persistSession: false,
autoRefreshToken: false,
detectSessionInUrl: false,
},
}
);
try {
const providers = toArray(message?.Saadetis?.Teenused.Teostaja);
const analysisGroups = providers.flatMap((provider) =>
toArray(provider.UuringuGrupp)
);
if (!message || !analysisGroups.length) {
return supabase.schema("audit").from("sync_entries").insert({
operation: "ANALYSES_SYNC",
comment: "No data received",
status: SyncStatus.Fail,
changed_by_role: "service_role",
});
}
for (const analysisGroup of analysisGroups) {
await saveAnalysisGroup(analysisGroup, supabase);
}
await supabase.schema("audit").from("sync_entries").insert({
operation: "ANALYSES_SYNC",
status: SyncStatus.Success,
changed_by_role: "service_role",
});
} catch (e) {
console.error(e);
await supabase
.schema("audit")
.from("sync_entries")
.insert({
operation: "ANALYSES_SYNC",
status: SyncStatus.Fail,
comment: JSON.stringify(e),
changed_by_role: "service_role",
});
}
}
function getLatestMessage(messages?: Message[]) {
if (!messages?.length) {
return null;

View File

@@ -0,0 +1,31 @@
"use server";
import * as yup from "yup";
import { companySchema } from "@/lib/validations/companySchema";
export async function submitCompanyRegistration(formData: FormData) {
const data = {
companyName: formData.get("companyName")?.toString() || "",
contactPerson: formData.get("contactPerson")?.toString() || "",
email: formData.get("email")?.toString() || "",
phone: formData.get("phone")?.toString() || "",
};
try {
await companySchema.validate(data, { abortEarly: false });
console.log("Valid data:", data);
} catch (validationError) {
if (validationError instanceof yup.ValidationError) {
const errors = validationError.inner.map((err) => ({
path: err.path,
message: err.message,
}));
throw new Error(
"Validation failed: " +
errors.map((e) => `${e.path}: ${e.message}`).join(", ")
);
}
throw validationError;
}
}

4
lib/types/audit.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum SyncStatus {
Success = "SUCCESS",
Fail = "FAIL",
}

6
lib/types/company.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface CompanySubmitData {
companyName: string;
contactPerson: string;
email: string;
phone?: string;
}

View File

@@ -20,3 +20,122 @@ export enum MedipostAction {
GetPrivateMessage = "GetPrivateMessage",
DeletePrivateMessage = "DeletePrivateMessage",
}
export type VoimalikVaartus = {
VaartusId: string;
Vaartus: "jah" | "ei";
VaartusJarjekord: number;
};
export type Sisendparameeter = {
"@_VastuseTyyp"?: "ARV" | "VABATEKST" | "KODEERITUD" | "AJAHETK";
"@_VastuseKoodistikuOID"?: string;
"@_VastuseKoodistikuNimi"?: string;
"@_URL"?: string;
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: number;
VoimalikVaartus: VoimalikVaartus[];
};
export type Kood = {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number; // float
Hind: number; // float
};
export type UuringuAlamElement = {
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: string;
Kood?: Kood[];
};
export type UuringuElement = {
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: string;
Kood?: Kood[];
UuringuElement?: UuringuAlamElement[];
};
export type Uuring = {
tellitav: "JAH" | "EI";
UuringuElement: UuringuElement; //1..1
MaterjalideGrupp?: MaterjalideGrupp[]; //0..n
};
export type UuringuGrupp = {
UuringuGruppId: string;
UuringuGruppNimi: string;
UuringuGruppJarjekord: number;
Uuring: Uuring | Uuring[]; //1..n
Kood?: Kood | Kood[]; //0..n
};
export type Konteiner = {
ProovinouKoodOID: string;
ProovinouKood: string;
KonteineriNimi: string;
KonteineriKirjeldus: string;
};
export type Materjal = {
MaterjaliTyypOID: string;
MaterjaliTyyp: string;
MaterjaliNimi: string;
KonteineriOmadus: string;
MaterjaliPaige: { Kohustuslik: "JAH" | "EI" }; //0..1
Konteiner?: Konteiner[]; //0..n
};
export type MaterjalideGrupp = {
vaikimisi: "JAH" | "EI";
Materjal: Materjal; //1..n
};
export type Teostaja = {
UuringuGrupp?: UuringuGrupp | UuringuGrupp[]; //0...n
Asutus: {
AsutuseId: string;
AsutuseNimi: string;
AsutuseKood: string;
AllyksuseNimi: string;
Telefon: string;
Aadress: string;
};
Sisendparameeter?: Sisendparameeter | Sisendparameeter[]; //0...n
};
export type MedipostPublicMessageResponse = {
"?xml": {
"@_version": string;
"@_encoding": "UTF-8";
"@_standalone"?: "yes" | "no";
};
ANSWER?: { CODE: number };
Saadetis?: {
Pais: {
Pakett: { "#text": "SL" | "OL" | "AL" | "ME" }; // SL - Teenused, OL - Tellimus (meie poolt saadetav saatekiri), AL - Vastus (saatekirja vastus), ME - Teade
Saatja: string;
Saaja: string;
Aeg: string;
SaadetisId: string;
};
Teenused: {
Teostaja: Teostaja | Teostaja[]; //1..n
};
};
};

View File

@@ -4,3 +4,8 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function toArray<T>(input?: T | T[] | null): T[] {
if (!input) return [];
return Array.isArray(input) ? input : [input];
}

View File

@@ -0,0 +1,8 @@
import * as yup from "yup";
export const companySchema = yup.object({
companyName: yup.string().required("Company name is required"),
contactPerson: yup.string().required("Contact person is required"),
email: yup.string().email("Invalid email").required("Email is required"),
phone: yup.string().optional(),
});

View File

@@ -61,6 +61,7 @@
"@tanstack/react-table": "^8.21.3",
"axios": "^1.9.0",
"clsx": "^2.1.1",
"fast-xml-parser": "^5.2.3",
"date-fns": "^4.1.0",
"lucide-react": "^0.510.0",
"next": "15.3.2",
@@ -73,10 +74,10 @@
"recharts": "2.15.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"xml-js": "^1.6.11",
"zod": "^3.24.4"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
@@ -85,6 +86,7 @@
"@types/node": "^22.15.18",
"@types/react": "19.1.4",
"@types/react-dom": "19.1.5",
"react-hook-form": "^7.57.0",
"babel-plugin-react-compiler": "19.1.0-rc.2",
"cssnano": "^7.0.7",
"pino-pretty": "^13.0.0",
@@ -92,7 +94,8 @@
"supabase": "^2.22.12",
"tailwindcss": "4.1.7",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"yup": "^1.6.1"
},
"prettier": "@kit/prettier-config",
"browserslist": [
@@ -100,4 +103,4 @@
"> 0.7%",
"not dead"
]
}
}

View File

@@ -1,5 +1,5 @@
import { cn } from '../../lib/utils';
import { LanguageSelector } from '@kit/ui/language-selector';
interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> {
logo?: React.ReactNode;
navigation?: React.ReactNode;
@@ -25,7 +25,8 @@ export const Header: React.FC<HeaderProps> = function ({
<div className="grid h-14 grid-cols-3 items-center">
<div className={'mx-auto md:mx-0'}>{logo}</div>
<div className="order-first md:order-none">{navigation}</div>
<div className="flex items-center justify-end gap-x-2">{actions}</div>
<div className="flex items-center justify-end gap-x-2"><div className="max-w-[100px]"><LanguageSelector /></div>{actions}</div>
</div>
</div>
</div>

View File

@@ -12,7 +12,7 @@ const buttonVariants = cva(
variants: {
variant: {
default:
'bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs',
'bg-primary text-primary-foreground font-medium hover:bg-primary/90 shadow-xs',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-xs',
outline:

66
pnpm-lock.yaml generated
View File

@@ -101,6 +101,9 @@ importers:
date-fns:
specifier: ^4.1.0
version: 4.1.0
fast-xml-parser:
specifier: ^5.2.3
version: 5.2.5
lucide-react:
specifier: ^0.510.0
version: 0.510.0(react@19.1.0)
@@ -134,9 +137,6 @@ importers:
tailwind-merge:
specifier: ^3.3.0
version: 3.3.0
xml-js:
specifier: ^1.6.11
version: 1.6.11
zod:
specifier: ^3.24.4
version: 3.25.56
@@ -189,6 +189,9 @@ importers:
typescript:
specifier: ^5.8.3
version: 5.8.3
yup:
specifier: ^1.6.1
version: 1.6.1
packages/analytics:
devDependencies:
@@ -5362,6 +5365,10 @@ packages:
fast-uri@3.0.6:
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
fast-xml-parser@5.2.5:
resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==}
hasBin: true
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -6727,6 +6734,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
property-expr@2.0.6:
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
prosemirror-commands@1.7.1:
resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
@@ -6976,9 +6986,6 @@ packages:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -7176,6 +7183,9 @@ packages:
'@types/node':
optional: true
strnum@2.1.1:
resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==}
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -7273,6 +7283,9 @@ packages:
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
tiny-invariant@1.0.6:
resolution: {integrity: sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==}
@@ -7290,6 +7303,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@@ -7354,6 +7370,10 @@ packages:
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
engines: {node: '>=8'}
type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -7579,10 +7599,6 @@ packages:
utf-8-validate:
optional: true
xml-js@1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@@ -7639,6 +7655,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
yup@1.6.1:
resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==}
zod@3.25.56:
resolution: {integrity: sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==}
@@ -12494,6 +12513,10 @@ snapshots:
fast-uri@3.0.6: {}
fast-xml-parser@5.2.5:
dependencies:
strnum: 2.1.1
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -13960,6 +13983,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
property-expr@2.0.6: {}
prosemirror-commands@1.7.1:
dependencies:
prosemirror-model: 1.25.1
@@ -14266,8 +14291,6 @@ snapshots:
safe-stable-stringify@2.5.0: {}
sax@1.4.1: {}
scheduler@0.26.0: {}
schema-utils@4.3.2:
@@ -14537,6 +14560,8 @@ snapshots:
optionalDependencies:
'@types/node': 22.15.30
strnum@2.1.1: {}
styled-jsx@5.1.6(@babel/core@7.27.4)(react@19.1.0):
dependencies:
client-only: 0.0.1
@@ -14626,6 +14651,8 @@ snapshots:
dependencies:
real-require: 0.2.0
tiny-case@1.0.3: {}
tiny-invariant@1.0.6: {}
tiny-invariant@1.3.3: {}
@@ -14641,6 +14668,8 @@ snapshots:
dependencies:
is-number: 7.0.0
toposort@2.0.2: {}
totalist@3.0.1: {}
tr46@0.0.3: {}
@@ -14693,6 +14722,8 @@ snapshots:
type-fest@0.7.1: {}
type-fest@2.19.0: {}
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -15006,10 +15037,6 @@ snapshots:
ws@8.18.2: {}
xml-js@1.6.11:
dependencies:
sax: 1.4.1
xtend@4.0.2: {}
y-prosemirror@1.3.5(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27):
@@ -15056,6 +15083,13 @@ snapshots:
yocto-queue@0.1.0: {}
yup@1.6.1:
dependencies:
property-expr: 2.0.6
tiny-case: 1.0.3
toposort: 2.0.2
type-fest: 2.19.0
zod@3.25.56: {}
zwitch@2.0.4: {}

View File

@@ -0,0 +1,16 @@
export const MedReportSmallLogo = () => {
return (
<svg
width="21"
height="21"
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20.7002 11.7002C15.7296 11.7002 11.7002 15.7296 11.7002 20.7002H8.10059C8.10059 13.7414 13.7414 8.10059 20.7002 8.10059V11.7002ZM12.8994 0.299805C12.8994 7.25859 7.25859 12.8994 0.299805 12.8994V9.2998C5.27037 9.2998 9.2998 5.27037 9.2998 0.299805H12.8994Z"
fill="#0A5328"
/>
</svg>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/assets/success.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -111,5 +111,7 @@
"languageDescription": "Choose your preferred language",
"noTeamsYet": "You don't have any teams yet.",
"createTeam": "Create a team to get started.",
"createTeamButtonLabel": "Create a Team"
"createTeamButtonLabel": "Create a Team",
"createCompanyAccount": "Create Company Account",
"requestCompanyAccount": "Request Company Account"
}

View File

@@ -35,5 +35,6 @@
"contactSuccessDescription": "We have received your message and will get back to you as soon as possible",
"contactErrorDescription": "An error occurred while sending your message. Please try again later",
"footerDescription": "Here you can add a description about your company or product",
"copyright": "© Copyright {{year}} {{product}}. All Rights Reserved."
"copyright": "© Copyright {{year}} {{product}}. All Rights Reserved.",
"heroSubtitle": "A simple, convenient, and quick overview of your health condition"
}

View File

@@ -0,0 +1,117 @@
{
"accountTabLabel": "Account Settings",
"accountTabDescription": "Manage your account settings",
"homePage": "Home",
"billingTab": "Billing",
"settingsTab": "Settings",
"multiFactorAuth": "Multi-Factor Authentication",
"multiFactorAuthDescription": "Set up Multi-Factor Authentication method to further secure your account",
"updateProfileSuccess": "Profile successfully updated",
"updateProfileError": "Encountered an error. Please try again",
"updatePasswordSuccess": "Password update request successful",
"updatePasswordSuccessMessage": "Your password has been successfully updated!",
"updatePasswordError": "Encountered an error. Please try again",
"updatePasswordLoading": "Updating password...",
"updateProfileLoading": "Updating profile...",
"name": "Your Name",
"nameDescription": "Update your name to be displayed on your profile",
"emailLabel": "Email Address",
"accountImage": "Your Profile Picture",
"accountImageDescription": "Please choose a photo to upload as your profile picture.",
"profilePictureHeading": "Upload a Profile Picture",
"profilePictureSubheading": "Choose a photo to upload as your profile picture.",
"updateProfileSubmitLabel": "Update Profile",
"updatePasswordCardTitle": "Update your Password",
"updatePasswordCardDescription": "Update your password to keep your account secure.",
"currentPassword": "Current Password",
"newPassword": "New Password",
"repeatPassword": "Repeat New Password",
"repeatPasswordDescription": "Please repeat your new password to confirm it",
"yourPassword": "Your Password",
"updatePasswordSubmitLabel": "Update Password",
"updateEmailCardTitle": "Update your Email",
"updateEmailCardDescription": "Update your email address you use to login to your account",
"newEmail": "Your New Email",
"repeatEmail": "Repeat Email",
"updateEmailSubmitLabel": "Update Email Address",
"updateEmailSuccess": "Email update request successful",
"updateEmailSuccessMessage": "We sent you an email to confirm your new email address. Please check your inbox and click on the link to confirm your new email address.",
"updateEmailLoading": "Updating your email...",
"updateEmailError": "Email not updated. Please try again",
"passwordNotMatching": "Passwords do not match. Make sure you're using the correct password",
"emailNotMatching": "Emails do not match. Make sure you're using the correct email",
"passwordNotChanged": "Your password has not changed",
"emailsNotMatching": "Emails do not match. Make sure you're using the correct email",
"cannotUpdatePassword": "You cannot update your password because your account is not linked to any.",
"setupMfaButtonLabel": "Setup a new Factor",
"multiFactorSetupErrorHeading": "Setup Failed",
"multiFactorSetupErrorDescription": "Sorry, there was an error while setting up your factor. Please try again.",
"multiFactorAuthHeading": "Secure your account with Multi-Factor Authentication",
"multiFactorModalHeading": "Use your authenticator app to scan the QR code below. Then enter the code generated.",
"factorNameLabel": "A memorable name to identify this factor",
"factorNameHint": "Use an easy-to-remember name to easily identify this factor in the future. Ex. iPhone 14",
"factorNameSubmitLabel": "Set factor name",
"unenrollTooltip": "Unenroll this factor",
"unenrollingFactor": "Unenrolling factor...",
"unenrollFactorSuccess": "Factor successfully unenrolled",
"unenrollFactorError": "Unenrolling factor failed",
"factorsListError": "Error loading factors list",
"factorsListErrorDescription": "Sorry, we couldn't load the factors list. Please try again.",
"factorName": "Factor Name",
"factorType": "Type",
"factorStatus": "Status",
"mfaEnabledSuccessTitle": "Multi-Factor authentication is enabled",
"mfaEnabledSuccessDescription": "Congratulations! You have successfully enrolled in the multi factor authentication process. You will now be able to access your account with a combination of your password and an authentication code sent to your phone number.",
"verificationCode": "Verification Code",
"addEmailAddress": "Add Email address",
"verifyActivationCodeDescription": "Enter the 6-digit code generated by your authenticator app in the field above",
"loadingFactors": "Loading factors...",
"enableMfaFactor": "Enable Factor",
"disableMfaFactor": "Disable Factor",
"qrCodeErrorHeading": "QR Code Error",
"qrCodeErrorDescription": "Sorry, we weren't able to generate the QR code",
"multiFactorSetupSuccess": "Factor successfully enrolled",
"submitVerificationCode": "Submit Verification Code",
"mfaEnabledSuccessAlert": "Multi-Factor authentication is enabled",
"verifyingCode": "Verifying code...",
"invalidVerificationCodeHeading": "Invalid Verification Code",
"invalidVerificationCodeDescription": "The verification code you entered is invalid. Please try again.",
"unenrollFactorModalHeading": "Unenroll Factor",
"unenrollFactorModalDescription": "You're about to unenroll this factor. You will not be able to use it to login to your account.",
"unenrollFactorModalBody": "You're about to unenroll this factor. You will not be able to use it to login to your account.",
"unenrollFactorModalButtonLabel": "Yes, unenroll factor",
"selectFactor": "Choose a factor to verify your identity",
"disableMfa": "Disable Multi-Factor Authentication",
"disableMfaButtonLabel": "Disable MFA",
"confirmDisableMfaButtonLabel": "Yes, disable MFA",
"disablingMfa": "Disabling Multi-Factor Authentication. Please wait...",
"disableMfaSuccess": "Multi-Factor Authentication successfully disabled",
"disableMfaError": "Sorry, we encountered an error. MFA has not been disabled.",
"sendingEmailVerificationLink": "Sending Email...",
"sendEmailVerificationLinkSuccess": "Verification link successfully sent",
"sendEmailVerificationLinkError": "Sorry, we weren't able to send you the email",
"sendVerificationLinkSubmitLabel": "Send Verification Link",
"sendVerificationLinkSuccessLabel": "Email sent! Check your Inbox",
"verifyEmailAlertHeading": "Please verify your email to enable MFA",
"verificationLinkAlertDescription": "Your email is not yet verified. Please verify your email to be able to set up Multi-Factor Authentication.",
"authFactorName": "Factor Name (optional)",
"authFactorNameHint": "Assign a name that helps you remember the phone number used",
"loadingUser": "Loading user details. Please wait...",
"linkPhoneNumber": "Link Phone Number",
"dangerZone": "Danger Zone",
"dangerZoneDescription": "Some actions cannot be undone. Please be careful.",
"deleteAccount": "Delete your Account",
"deletingAccount": "Deleting account. Please wait...",
"deleteAccountDescription": "This will delete your account and the accounts you own. Furthermore, we will immediately cancel any active subscriptions. This action cannot be undone.",
"deleteProfileConfirmationInputLabel": "Type DELETE to confirm",
"deleteAccountErrorHeading": "Sorry, we couldn't delete your account",
"needsReauthentication": "Reauthentication Required",
"needsReauthenticationDescription": "You need to reauthenticate to change your password. Please sign out and sign in again to change your password.",
"language": "Language",
"languageDescription": "Choose your preferred language",
"noTeamsYet": "You don't have any teams yet.",
"createTeam": "Create a team to get started.",
"createTeamButtonLabel": "Create a Team",
"createCompanyAccount": "Create Company Account",
"requestCompanyAccount": "Küsi pakkumist"
}

View File

@@ -0,0 +1,90 @@
{
"signUpHeading": "Create an account",
"signUp": "Sign Up",
"signUpSubheading": "Fill the form below to create an account.",
"signInHeading": "Sign in to your account",
"signInSubheading": "Welcome back! Please enter your details",
"signIn": "Sign In",
"getStarted": "Get started",
"updatePassword": "Update Password",
"signOut": "Sign out",
"signingIn": "Signing in...",
"signingUp": "Signing up...",
"doNotHaveAccountYet": "Do not have an account yet?",
"alreadyHaveAnAccount": "Already have an account?",
"signUpToAcceptInvite": "Please sign in/up to accept the invite",
"clickToAcceptAs": "Click the button below to accept the invite with as <b>{{email}}</b>",
"acceptInvite": "Accept invite",
"acceptingInvite": "Accepting Invite...",
"acceptInviteSuccess": "Invite successfully accepted",
"acceptInviteError": "Error encountered while accepting invite",
"acceptInviteWithDifferentAccount": "Want to accept the invite with a different account?",
"alreadyHaveAccountStatement": "I already have an account, I want to sign in instead",
"doNotHaveAccountStatement": "I do not have an account, I want to sign up instead",
"signInWithProvider": "Sign in with {{provider}}",
"signInWithPhoneNumber": "Sign in with Phone Number",
"signInWithEmail": "Sign in with Email",
"signUpWithEmail": "Sign up with Email",
"passwordHint": "Ensure it's at least 8 characters",
"repeatPasswordHint": "Type your password again",
"repeatPassword": "Repeat password",
"passwordForgottenQuestion": "Password forgotten?",
"passwordResetLabel": "Reset Password",
"passwordResetSubheading": "Enter your email address below. You will receive a link to reset your password.",
"passwordResetSuccessMessage": "Check your Inbox! We emailed you a link for resetting your Password.",
"passwordRecoveredQuestion": "Password recovered?",
"passwordLengthError": "Please provide a password with at least 6 characters",
"sendEmailLink": "Send Email Link",
"sendingEmailLink": "Sending Email Link...",
"sendLinkSuccessDescription": "Check your email, we just sent you a link. Follow the link to sign in.",
"sendLinkSuccess": "We sent you a link by email",
"sendLinkSuccessToast": "Link successfully sent",
"getNewLink": "Get a new link",
"verifyCodeHeading": "Verify your account",
"verificationCode": "Verification Code",
"verificationCodeHint": "Enter the code we sent you by SMS",
"verificationCodeSubmitButtonLabel": "Submit Verification Code",
"sendingMfaCode": "Sending Verification Code...",
"verifyingMfaCode": "Verifying code...",
"sendMfaCodeError": "Sorry, we couldn't send you a verification code",
"verifyMfaCodeSuccess": "Code verified! Signing you in...",
"verifyMfaCodeError": "Ops! It looks like the code is not correct",
"reauthenticate": "Reauthenticate",
"reauthenticateDescription": "For security reasons, we need you to re-authenticate",
"errorAlertHeading": "Sorry, we could not authenticate you",
"emailConfirmationAlertHeading": "We sent you a confirmation email.",
"emailConfirmationAlertBody": "Welcome! Please check your email and click the link to verify your account.",
"resendLink": "Resend link",
"resendLinkSuccessDescription": "We sent you a new link to your email! Follow the link to sign in.",
"resendLinkSuccess": "Check your email!",
"authenticationErrorAlertHeading": "Authentication Error",
"authenticationErrorAlertBody": "Sorry, we could not authenticate you. Please try again.",
"sendEmailCode": "Get code to your Email",
"sendingEmailCode": "Sending code...",
"resetPasswordError": "Sorry, we could not reset your password. Please try again.",
"emailPlaceholder": "your@email.com",
"inviteAlertHeading": "You have been invited to join a team",
"inviteAlertBody": "Please sign in or sign up to accept the invite and join the team.",
"acceptTermsAndConditions": "I accept the <TermsOfServiceLink /> and <PrivacyPolicyLink />",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"orContinueWith": "Or continue with",
"redirecting": "You're in! Please wait...",
"errors": {
"Invalid login credentials": "The credentials entered are invalid",
"User already registered": "This credential is already in use. Please try with another one.",
"Email not confirmed": "Please confirm your email address before signing in",
"default": "We have encountered an error. Please ensure you have a working internet connection and try again",
"generic": "Sorry, we weren't able to authenticate you. Please try again.",
"link": "Sorry, we encountered an error while sending your link. Please try again.",
"codeVerifierMismatch": "It looks like you're trying to sign in using a different browser than the one you used to request the sign in link. Please try again using the same browser.",
"minPasswordLength": "Password must be at least 8 characters long",
"passwordsDoNotMatch": "The passwords do not match",
"minPasswordNumbers": "Password must contain at least one number",
"minPasswordSpecialChars": "Password must contain at least one special character",
"uppercasePassword": "Password must contain at least one uppercase letter",
"insufficient_aal": "Please sign-in with your current multi-factor authentication to perform this action",
"otp_expired": "The email link has expired. Please try again.",
"same_password": "The password cannot be the same as the current password"
}
}

View File

@@ -0,0 +1,120 @@
{
"subscriptionTabSubheading": "Manage your Subscription and Billing",
"planCardTitle": "Your Plan",
"planCardDescription": "Below are the details of your current plan. You can change your plan or cancel your subscription at any time.",
"planRenewal": "Renews every {{interval}} at {{price}}",
"planDetails": "Plan Details",
"checkout": "Proceed to Checkout",
"trialEndsOn": "Your trial ends on",
"billingPortalCardButton": "Visit Billing Portal",
"billingPortalCardTitle": "Manage your Billing Details",
"billingPortalCardDescription": "Visit your Billing Portal to manage your subscription and billing. You can update or cancel your plan, or download your invoices.",
"cancelAtPeriodEndDescription": "Your subscription is scheduled to be canceled on {{- endDate }}.",
"renewAtPeriodEndDescription": "Your subscription is scheduled to be renewed on {{- endDate }}",
"noPermissionsAlertHeading": "You don't have permissions to change the billing settings",
"noPermissionsAlertBody": "Please contact your account owner to change the billing settings for your account.",
"checkoutSuccessTitle": "Done! You're all set.",
"checkoutSuccessDescription": "Thank you for subscribing, we have successfully processed your subscription! A confirmation email will be sent to {{ customerEmail }}.",
"checkoutSuccessBackButton": "Proceed to App",
"cannotManageBillingAlertTitle": "You cannot manage billing",
"cannotManageBillingAlertDescription": "You do not have permissions to manage billing. Please contact your account owner.",
"manageTeamPlan": "Manage your Team Plan",
"manageTeamPlanDescription": "Choose a plan that fits your team's needs. You can upgrade or downgrade your plan at any time.",
"basePlan": "Base Plan",
"billingInterval": {
"label": "Choose your billing interval",
"month": "Billed monthly",
"year": "Billed yearly"
},
"perMonth": "month",
"custom": "Custom Plan",
"lifetime": "Lifetime",
"trialPeriod": "{{period}} day trial",
"perPeriod": "per {{period}}",
"redirectingToPayment": "Redirecting to checkout. Please wait...",
"proceedToPayment": "Proceed to Payment",
"startTrial": "Start Trial",
"perTeamMember": "Per team member",
"perUnit": "Per {{unit}} usage",
"teamMembers": "Team Members",
"includedUpTo": "Up to {{upTo}} {{unit}} included in the plan",
"fromPreviousTierUpTo": "for each {{unit}} for the next {{ upTo }} {{ unit }}",
"andAbove": "above {{ previousTier }} {{ unit }}",
"startingAtPriceUnit": "Starting at {{price}}/{{unit}}",
"priceUnit": "{{price}}/{{unit}}",
"forEveryUnit": "for every {{ unit }}",
"setupFee": "plus a {{ setupFee }} setup fee",
"perUnitIncluded": "({{included}} included)",
"featuresLabel": "Features",
"detailsLabel": "Details",
"planPickerLabel": "Pick your preferred plan",
"planCardLabel": "Manage your Plan",
"planPickerAlertErrorTitle": "Error requesting checkout",
"planPickerAlertErrorDescription": "There was an error requesting checkout. Please try again later.",
"subscriptionCancelled": "Subscription Cancelled",
"cancelSubscriptionDate": "Your subscription will be cancelled at the end of the period",
"noPlanChosen": "Please choose a plan",
"noIntervalPlanChosen": "Please choose a billing interval",
"status": {
"free": {
"badge": "Free Plan",
"heading": "You are currently on the Free Plan",
"description": "You're on a free plan. You can upgrade to a paid plan at any time."
},
"active": {
"badge": "Active",
"heading": "Your subscription is active",
"description": "Your subscription is active. You can manage your subscription and billing in the Customer Portal."
},
"trialing": {
"badge": "Trial",
"heading": "You're on a trial",
"description": "You can enjoy the benefits of plan until the trial ends"
},
"past_due": {
"badge": "Past Due",
"heading": "Your invoice is past due",
"description": "Your invoice is past due. Please update your payment method."
},
"canceled": {
"badge": "Canceled",
"heading": "Your subscription is canceled",
"description": "Your subscription is canceled. It is scheduled to end at end of the billing period."
},
"unpaid": {
"badge": "Unpaid",
"heading": "Your invoice is unpaid",
"description": "Your invoice is unpaid. Please update your payment method."
},
"incomplete": {
"badge": "Incomplete",
"heading": "We're waiting for your payment",
"description": "We're waiting for your payment to go through. Please bear with us."
},
"incomplete_expired": {
"badge": "Expired",
"heading": "Your payment has expired",
"description": "Your payment has expired. Please update your payment method."
},
"paused": {
"badge": "Paused",
"heading": "Your subscription is paused",
"description": "Your subscription is paused. You can resume it at any time."
},
"succeeded": {
"badge": "Succeeded",
"heading": "Your payment was successful",
"description": "Your payment was successful. Thank you for subscribing!"
},
"pending": {
"badge": "Pending",
"heading": "Your payment is pending",
"description": "Your payment is pending. Please bear with us."
},
"failed": {
"badge": "Failed",
"heading": "Your payment failed",
"description": "Your payment failed. Please update your payment method."
}
}
}

View File

@@ -0,0 +1,96 @@
{
"homeTabLabel": "Home",
"homeTabDescription": "Welcome to your home page",
"accountMembers": "Team Members",
"membersTabDescription": "Here you can manage the members of your team.",
"billingTabLabel": "Billing",
"billingTabDescription": "Manage your billing and subscription",
"dashboardTabLabel": "Dashboard",
"settingsTabLabel": "Settings",
"profileSettingsTabLabel": "Profile",
"subscriptionSettingsTabLabel": "Subscription",
"dashboardTabDescription": "An overview of your account's activity and performance across all your projects.",
"settingsTabDescription": "Manage your settings and preferences.",
"emailAddress": "Email Address",
"password": "Password",
"modalConfirmationQuestion": "Are you sure you want to continue?",
"imageInputLabel": "Click here to upload an image",
"cancel": "Cancel",
"clear": "Clear",
"notFound": "Not Found",
"backToHomePage": "Back to Home Page",
"goBack": "Go Back",
"genericServerError": "Sorry, something went wrong.",
"genericServerErrorHeading": "Sorry, something went wrong while processing your request. Please contact us if the issue persists.",
"pageNotFound": "Sorry, this page does not exist.",
"pageNotFoundSubHeading": "Apologies, the page you were looking for was not found",
"genericError": "Sorry, something went wrong.",
"genericErrorSubHeading": "Apologies, an error occurred while processing your request. Please contact us if the issue persists.",
"anonymousUser": "Anonymous",
"tryAgain": "Try Again",
"theme": "Theme",
"lightTheme": "Light",
"darkTheme": "Dark",
"systemTheme": "System",
"expandSidebar": "Expand Sidebar",
"collapseSidebar": "Collapse Sidebar",
"documentation": "Documentation",
"getStarted": "Get Started",
"getStartedWithPlan": "Get Started with {{plan}}",
"retry": "Retry",
"contactUs": "Contact Us",
"loading": "Loading. Please wait...",
"yourAccounts": "Your Accounts",
"continue": "Continue",
"skip": "Skip",
"signedInAs": "Signed in as",
"pageOfPages": "Page {{page}} of {{total}}",
"noData": "No data available",
"pageNotFoundHeading": "Ouch! :|",
"errorPageHeading": "Ouch! :|",
"notifications": "Notifications",
"noNotifications": "No notifications",
"justNow": "Just now",
"newVersionAvailable": "New version available",
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
"newVersionSubmitButton": "Reload and Update",
"back": "Back",
"routes": {
"home": "Home",
"account": "Account",
"members": "Members",
"billing": "Billing",
"dashboard": "Dashboard",
"settings": "Settings",
"profile": "Profile",
"application": "Application"
},
"roles": {
"owner": {
"label": "Owner"
},
"member": {
"label": "Member"
}
},
"otp": {
"requestVerificationCode": "Request Verification Code",
"requestVerificationCodeDescription": "We must verify your identity to continue with this action. We'll send a verification code to the email address {{email}}.",
"sendingCode": "Sending Code...",
"sendVerificationCode": "Send Verification Code",
"enterVerificationCode": "Enter Verification Code",
"codeSentToEmail": "We've sent a verification code to the email address {{email}}.",
"verificationCode": "Verification Code",
"enterCodeFromEmail": "Enter the 6-digit code we sent to your email.",
"verifying": "Verifying...",
"verifyCode": "Verify Code",
"requestNewCode": "Request New Code",
"errorSendingCode": "Error sending code. Please try again."
},
"cookieBanner": {
"title": "Hey, we use cookies 🍪",
"description": "This website uses cookies to ensure you get the best experience on our website.",
"reject": "Reject",
"accept": "Accept"
}
}

View File

@@ -0,0 +1,40 @@
{
"blog": "Blog",
"blogSubtitle": "News and updates about the platform",
"documentation": "Documentation",
"documentationSubtitle": "Tutorials and guide to get started with the platform",
"faq": "FAQ",
"faqSubtitle": "Frequently asked questions about the platform",
"pricing": "Pricing",
"pricingSubtitle": "Pricing plans and payment options",
"backToBlog": "Back to blog",
"noPosts": "No posts found",
"blogPaginationNext": "Next Page",
"blogPaginationPrevious": "Previous Page",
"readMore": "Read more",
"contactFaq": "If you have any questions, please contact us",
"contact": "Contact",
"about": "About",
"product": "Product",
"legal": "Legal",
"termsOfService": "Terms of Service",
"termsOfServiceDescription": "Our terms and conditions",
"cookiePolicy": "Cookie Policy",
"cookiePolicyDescription": "Our cookie policy and how we use them",
"privacyPolicy": "Privacy Policy",
"privacyPolicyDescription": "Our privacy policy and how we use your data",
"contactDescription": "Contact us for any questions or feedback",
"contactHeading": "Send us a message",
"contactSubheading": "We will get back to you as soon as possible",
"contactName": "Your Name",
"contactEmail": "Your Email",
"contactMessage": "Your Message",
"sendMessage": "Send Message",
"contactSuccess": "Your message has been sent successfully",
"contactError": "An error occurred while sending your message",
"contactSuccessDescription": "We have received your message and will get back to you as soon as possible",
"contactErrorDescription": "An error occurred while sending your message. Please try again later",
"footerDescription": "Here you can add a description about your company or product",
"copyright": "© Copyright {{year}} {{product}}. All Rights Reserved.",
"heroSubtitle": "Lihtne, mugav ja kiire ülevaade Sinu tervise seisundist"
}

View File

@@ -0,0 +1,163 @@
{
"home": {
"pageTitle": "Home"
},
"settings": {
"pageTitle": "Settings",
"pageDescription": "Manage your Team details",
"teamLogo": "Team Logo",
"teamLogoDescription": "Update your team's logo to make it easier to identify",
"teamName": "Team Name",
"teamNameDescription": "Update your team's name",
"dangerZone": "Danger Zone",
"dangerZoneDescription": "This section contains actions that are irreversible"
},
"members": {
"pageTitle": "Members"
},
"billing": {
"pageTitle": "Billing"
},
"yourTeams": "Your Teams ({{teamsCount}})",
"createTeam": "Create a Team",
"creatingTeam": "Creating Team...",
"personalAccount": "Personal Account",
"searchAccount": "Search Account...",
"membersTabLabel": "Members",
"memberName": "Name",
"youLabel": "You",
"emailLabel": "Email",
"roleLabel": "Role",
"primaryOwnerLabel": "Primary Owner",
"joinedAtLabel": "Joined at",
"invitedAtLabel": "Invited at",
"inviteMembersPageSubheading": "Invite members to your Team",
"createTeamModalHeading": "Create Team",
"createTeamModalDescription": "Create a new Team to manage your projects and members.",
"teamNameLabel": "Team Name",
"teamNameDescription": "Your team name should be unique and descriptive",
"createTeamSubmitLabel": "Create Team",
"createTeamSuccess": "Team created successfully",
"createTeamError": "Team not created. Please try again.",
"createTeamLoading": "Creating team...",
"settingsPageLabel": "General",
"createTeamDropdownLabel": "New team",
"changeRole": "Change Role",
"removeMember": "Remove from Account",
"inviteMembersSuccess": "Members invited successfully!",
"inviteMembersError": "Sorry, we encountered an error! Please try again",
"inviteMembersLoading": "Inviting members...",
"removeInviteButtonLabel": "Remove invite",
"addAnotherMemberButtonLabel": "Add another one",
"inviteMembersButtonLabel": "Send Invites",
"removeMemberModalHeading": "You are removing this user",
"removeMemberModalDescription": "Remove this member from the team. They will no longer have access to the team.",
"removeMemberSuccessMessage": "Member removed successfully",
"removeMemberErrorMessage": "Sorry, we encountered an error. Please try again",
"removeMemberErrorHeading": "Sorry, we couldn't remove the selected member.",
"removeMemberLoadingMessage": "Removing member...",
"removeMemberSubmitLabel": "Remove User from Team",
"chooseDifferentRoleError": "Role is the same as the current one",
"updateRole": "Update Role",
"updateRoleLoadingMessage": "Updating role...",
"updateRoleSuccessMessage": "Role updated successfully",
"updatingRoleErrorMessage": "Sorry, we encountered an error. Please try again.",
"updateMemberRoleModalHeading": "Update Member's Role",
"updateMemberRoleModalDescription": "Change the role of the selected member. The role determines the permissions of the member.",
"roleMustBeDifferent": "Role must be different from the current one",
"memberRoleInputLabel": "Member role",
"updateRoleDescription": "Pick a role for this member.",
"updateRoleSubmitLabel": "Update Role",
"transferOwnership": "Transfer Ownership",
"transferOwnershipDescription": "Transfer ownership of the team to another member.",
"transferOwnershipInputLabel": "Please type TRANSFER to confirm the transfer of ownership.",
"transferOwnershipInputDescription": "By transferring ownership, you will no longer be the primary owner of the team.",
"deleteInvitation": "Delete Invitation",
"deleteInvitationDialogDescription": "You are about to delete the invitation. The user will no longer be able to join the team.",
"deleteInviteSuccessMessage": "Invite deleted successfully",
"deleteInviteErrorMessage": "Invite not deleted. Please try again.",
"deleteInviteLoadingMessage": "Deleting invite. Please wait...",
"confirmDeletingMemberInvite": "You are deleting the invite to <b>{{ email }}</b>",
"transferOwnershipDisclaimer": "You are transferring ownership of the selected team to <b>{{ member }}</b>.",
"transferringOwnership": "Transferring ownership...",
"transferOwnershipSuccess": "Ownership successfully transferred",
"transferOwnershipError": "Sorry, we could not transfer ownership to the selected member. Please try again.",
"deleteInviteSubmitLabel": "Delete Invite",
"youBadgeLabel": "You",
"updateTeamLoadingMessage": "Updating Team...",
"updateTeamSuccessMessage": "Team successfully updated",
"updateTeamErrorMessage": "Could not update Team. Please try again.",
"updateLogoErrorMessage": "Could not update Logo. Please try again.",
"teamNameInputLabel": "Team Name",
"teamLogoInputHeading": "Upload your team's Logo",
"teamLogoInputSubheading": "Please choose a photo to upload as your team logo.",
"updateTeamSubmitLabel": "Update Team",
"inviteMembersHeading": "Invite Members to your Team",
"inviteMembersDescription": "Invite members to your team by entering their email and role.",
"emailPlaceholder": "member@email.com",
"membersPageHeading": "Members",
"inviteMembersButton": "Invite Members",
"invitingMembers": "Inviting members...",
"inviteMembersSuccessMessage": "Members invited successfully",
"inviteMembersErrorMessage": "Sorry, members could not be invited. Please try again.",
"pendingInvitesHeading": "Pending Invites",
"pendingInvitesDescription": " Here you can manage the pending invitations to your team.",
"noPendingInvites": "No pending invites found",
"loadingMembers": "Loading members...",
"loadMembersError": "Sorry, we couldn't fetch your team's members.",
"loadInvitedMembersError": "Sorry, we couldn't fetch your team's invited members.",
"loadingInvitedMembers": "Loading invited members...",
"invitedBadge": "Invited",
"duplicateInviteEmailError": "You have already entered this email address",
"invitingOwnAccountError": "Hey, that's your email!",
"dangerZone": "Danger Zone",
"dangerZoneSubheading": "Delete or leave your team",
"deleteTeam": "Delete Team",
"deleteTeamDescription": "This action cannot be undone. All data associated with this team will be deleted.",
"deletingTeam": "Deleting team",
"deleteTeamModalHeading": "Deleting Team",
"deletingTeamDescription": "You are about to delete the team {{ teamName }}. This action cannot be undone.",
"deleteTeamInputField": "Type the name of the team to confirm",
"leaveTeam": "Leave Team",
"leavingTeamModalHeading": "Leaving Team",
"leavingTeamModalDescription": "You are about to leave this team. You will no longer have access to it.",
"leaveTeamDescription": "Click the button below to leave the team. Remember, you will no longer have access to it and will need to be re-invited to join.",
"deleteTeamDisclaimer": "You are deleting the team {{ teamName }}. This action cannot be undone.",
"leaveTeamDisclaimer": "You are leaving the team {{ teamName }}. You will no longer have access to it.",
"deleteTeamErrorHeading": "Sorry, we couldn't delete your team.",
"leaveTeamErrorHeading": "Sorry, we couldn't leave your team.",
"searchMembersPlaceholder": "Search members",
"createTeamErrorHeading": "Sorry, we couldn't create your team.",
"createTeamErrorMessage": "We encountered an error creating your team. Please try again.",
"transferTeamErrorHeading": "Sorry, we couldn't transfer ownership of your team.",
"transferTeamErrorMessage": "We encountered an error transferring ownership of your team. Please try again.",
"updateRoleErrorHeading": "Sorry, we couldn't update the role of the selected member.",
"updateRoleErrorMessage": "We encountered an error updating the role of the selected member. Please try again.",
"searchInvitations": "Search Invitations",
"updateInvitation": "Update Invitation",
"removeInvitation": "Remove Invitation",
"acceptInvitation": "Accept Invitation",
"renewInvitation": "Renew Invitation",
"resendInvitation": "Resend Invitation",
"expiresAtLabel": "Expires at",
"expired": "Expired",
"active": "Active",
"inviteStatus": "Status",
"inviteNotFoundOrExpired": "Invite not found or expired",
"inviteNotFoundOrExpiredDescription": "The invite you are looking for is either expired or does not exist. Please contact the team owner to renew the invite.",
"backToHome": "Back to Home",
"renewInvitationDialogDescription": "You are about to renew the invitation to {{ email }}. The user will be able to join the team.",
"renewInvitationErrorTitle": "Sorry, we couldn't renew the invitation.",
"renewInvitationErrorDescription": "We encountered an error renewing the invitation. Please try again.",
"signInWithDifferentAccount": "Sign in with a different account",
"signInWithDifferentAccountDescription": "If you wish to accept the invitation with a different account, please sign out and back in with the account you wish to use.",
"acceptInvitationHeading": "Accept Invitation to join {{accountName}}",
"acceptInvitationDescription": "You have been invited to join the team {{accountName}}. If you wish to accept the invitation, please click the button below.",
"continueAs": "Continue as {{email}}",
"joinTeamAccount": "Join Team",
"joiningTeam": "Joining team...",
"leaveTeamInputLabel": "Please type LEAVE to confirm leaving the team.",
"leaveTeamInputDescription": "By leaving the team, you will no longer have access to it.",
"reservedNameError": "This name is reserved. Please choose a different one.",
"specialCharactersError": "This name cannot contain special characters. Please choose a different one."
}

View File

@@ -0,0 +1,117 @@
{
"accountTabLabel": "Account Settings",
"accountTabDescription": "Manage your account settings",
"homePage": "Home",
"billingTab": "Billing",
"settingsTab": "Settings",
"multiFactorAuth": "Multi-Factor Authentication",
"multiFactorAuthDescription": "Set up Multi-Factor Authentication method to further secure your account",
"updateProfileSuccess": "Profile successfully updated",
"updateProfileError": "Encountered an error. Please try again",
"updatePasswordSuccess": "Password update request successful",
"updatePasswordSuccessMessage": "Your password has been successfully updated!",
"updatePasswordError": "Encountered an error. Please try again",
"updatePasswordLoading": "Updating password...",
"updateProfileLoading": "Updating profile...",
"name": "Your Name",
"nameDescription": "Update your name to be displayed on your profile",
"emailLabel": "Email Address",
"accountImage": "Your Profile Picture",
"accountImageDescription": "Please choose a photo to upload as your profile picture.",
"profilePictureHeading": "Upload a Profile Picture",
"profilePictureSubheading": "Choose a photo to upload as your profile picture.",
"updateProfileSubmitLabel": "Update Profile",
"updatePasswordCardTitle": "Update your Password",
"updatePasswordCardDescription": "Update your password to keep your account secure.",
"currentPassword": "Current Password",
"newPassword": "New Password",
"repeatPassword": "Repeat New Password",
"repeatPasswordDescription": "Please repeat your new password to confirm it",
"yourPassword": "Your Password",
"updatePasswordSubmitLabel": "Update Password",
"updateEmailCardTitle": "Update your Email",
"updateEmailCardDescription": "Update your email address you use to login to your account",
"newEmail": "Your New Email",
"repeatEmail": "Repeat Email",
"updateEmailSubmitLabel": "Update Email Address",
"updateEmailSuccess": "Email update request successful",
"updateEmailSuccessMessage": "We sent you an email to confirm your new email address. Please check your inbox and click on the link to confirm your new email address.",
"updateEmailLoading": "Updating your email...",
"updateEmailError": "Email not updated. Please try again",
"passwordNotMatching": "Passwords do not match. Make sure you're using the correct password",
"emailNotMatching": "Emails do not match. Make sure you're using the correct email",
"passwordNotChanged": "Your password has not changed",
"emailsNotMatching": "Emails do not match. Make sure you're using the correct email",
"cannotUpdatePassword": "You cannot update your password because your account is not linked to any.",
"setupMfaButtonLabel": "Setup a new Factor",
"multiFactorSetupErrorHeading": "Setup Failed",
"multiFactorSetupErrorDescription": "Sorry, there was an error while setting up your factor. Please try again.",
"multiFactorAuthHeading": "Secure your account with Multi-Factor Authentication",
"multiFactorModalHeading": "Use your authenticator app to scan the QR code below. Then enter the code generated.",
"factorNameLabel": "A memorable name to identify this factor",
"factorNameHint": "Use an easy-to-remember name to easily identify this factor in the future. Ex. iPhone 14",
"factorNameSubmitLabel": "Set factor name",
"unenrollTooltip": "Unenroll this factor",
"unenrollingFactor": "Unenrolling factor...",
"unenrollFactorSuccess": "Factor successfully unenrolled",
"unenrollFactorError": "Unenrolling factor failed",
"factorsListError": "Error loading factors list",
"factorsListErrorDescription": "Sorry, we couldn't load the factors list. Please try again.",
"factorName": "Factor Name",
"factorType": "Type",
"factorStatus": "Status",
"mfaEnabledSuccessTitle": "Multi-Factor authentication is enabled",
"mfaEnabledSuccessDescription": "Congratulations! You have successfully enrolled in the multi factor authentication process. You will now be able to access your account with a combination of your password and an authentication code sent to your phone number.",
"verificationCode": "Verification Code",
"addEmailAddress": "Add Email address",
"verifyActivationCodeDescription": "Enter the 6-digit code generated by your authenticator app in the field above",
"loadingFactors": "Loading factors...",
"enableMfaFactor": "Enable Factor",
"disableMfaFactor": "Disable Factor",
"qrCodeErrorHeading": "QR Code Error",
"qrCodeErrorDescription": "Sorry, we weren't able to generate the QR code",
"multiFactorSetupSuccess": "Factor successfully enrolled",
"submitVerificationCode": "Submit Verification Code",
"mfaEnabledSuccessAlert": "Multi-Factor authentication is enabled",
"verifyingCode": "Verifying code...",
"invalidVerificationCodeHeading": "Invalid Verification Code",
"invalidVerificationCodeDescription": "The verification code you entered is invalid. Please try again.",
"unenrollFactorModalHeading": "Unenroll Factor",
"unenrollFactorModalDescription": "You're about to unenroll this factor. You will not be able to use it to login to your account.",
"unenrollFactorModalBody": "You're about to unenroll this factor. You will not be able to use it to login to your account.",
"unenrollFactorModalButtonLabel": "Yes, unenroll factor",
"selectFactor": "Choose a factor to verify your identity",
"disableMfa": "Disable Multi-Factor Authentication",
"disableMfaButtonLabel": "Disable MFA",
"confirmDisableMfaButtonLabel": "Yes, disable MFA",
"disablingMfa": "Disabling Multi-Factor Authentication. Please wait...",
"disableMfaSuccess": "Multi-Factor Authentication successfully disabled",
"disableMfaError": "Sorry, we encountered an error. MFA has not been disabled.",
"sendingEmailVerificationLink": "Sending Email...",
"sendEmailVerificationLinkSuccess": "Verification link successfully sent",
"sendEmailVerificationLinkError": "Sorry, we weren't able to send you the email",
"sendVerificationLinkSubmitLabel": "Send Verification Link",
"sendVerificationLinkSuccessLabel": "Email sent! Check your Inbox",
"verifyEmailAlertHeading": "Please verify your email to enable MFA",
"verificationLinkAlertDescription": "Your email is not yet verified. Please verify your email to be able to set up Multi-Factor Authentication.",
"authFactorName": "Factor Name (optional)",
"authFactorNameHint": "Assign a name that helps you remember the phone number used",
"loadingUser": "Loading user details. Please wait...",
"linkPhoneNumber": "Link Phone Number",
"dangerZone": "Danger Zone",
"dangerZoneDescription": "Some actions cannot be undone. Please be careful.",
"deleteAccount": "Delete your Account",
"deletingAccount": "Deleting account. Please wait...",
"deleteAccountDescription": "This will delete your account and the accounts you own. Furthermore, we will immediately cancel any active subscriptions. This action cannot be undone.",
"deleteProfileConfirmationInputLabel": "Type DELETE to confirm",
"deleteAccountErrorHeading": "Sorry, we couldn't delete your account",
"needsReauthentication": "Reauthentication Required",
"needsReauthenticationDescription": "You need to reauthenticate to change your password. Please sign out and sign in again to change your password.",
"language": "Language",
"languageDescription": "Choose your preferred language",
"noTeamsYet": "You don't have any teams yet.",
"createTeam": "Create a team to get started.",
"createTeamButtonLabel": "Create a Team",
"createCompanyAccount": "Create Company Account",
"requestCompanyAccount": "Request Company Account"
}

View File

@@ -0,0 +1,90 @@
{
"signUpHeading": "Create an account",
"signUp": "Sign Up",
"signUpSubheading": "Fill the form below to create an account.",
"signInHeading": "Sign in to your account",
"signInSubheading": "Welcome back! Please enter your details",
"signIn": "Sign In",
"getStarted": "Get started",
"updatePassword": "Update Password",
"signOut": "Sign out",
"signingIn": "Signing in...",
"signingUp": "Signing up...",
"doNotHaveAccountYet": "Do not have an account yet?",
"alreadyHaveAnAccount": "Already have an account?",
"signUpToAcceptInvite": "Please sign in/up to accept the invite",
"clickToAcceptAs": "Click the button below to accept the invite with as <b>{{email}}</b>",
"acceptInvite": "Accept invite",
"acceptingInvite": "Accepting Invite...",
"acceptInviteSuccess": "Invite successfully accepted",
"acceptInviteError": "Error encountered while accepting invite",
"acceptInviteWithDifferentAccount": "Want to accept the invite with a different account?",
"alreadyHaveAccountStatement": "I already have an account, I want to sign in instead",
"doNotHaveAccountStatement": "I do not have an account, I want to sign up instead",
"signInWithProvider": "Sign in with {{provider}}",
"signInWithPhoneNumber": "Sign in with Phone Number",
"signInWithEmail": "Sign in with Email",
"signUpWithEmail": "Sign up with Email",
"passwordHint": "Ensure it's at least 8 characters",
"repeatPasswordHint": "Type your password again",
"repeatPassword": "Repeat password",
"passwordForgottenQuestion": "Password forgotten?",
"passwordResetLabel": "Reset Password",
"passwordResetSubheading": "Enter your email address below. You will receive a link to reset your password.",
"passwordResetSuccessMessage": "Check your Inbox! We emailed you a link for resetting your Password.",
"passwordRecoveredQuestion": "Password recovered?",
"passwordLengthError": "Please provide a password with at least 6 characters",
"sendEmailLink": "Send Email Link",
"sendingEmailLink": "Sending Email Link...",
"sendLinkSuccessDescription": "Check your email, we just sent you a link. Follow the link to sign in.",
"sendLinkSuccess": "We sent you a link by email",
"sendLinkSuccessToast": "Link successfully sent",
"getNewLink": "Get a new link",
"verifyCodeHeading": "Verify your account",
"verificationCode": "Verification Code",
"verificationCodeHint": "Enter the code we sent you by SMS",
"verificationCodeSubmitButtonLabel": "Submit Verification Code",
"sendingMfaCode": "Sending Verification Code...",
"verifyingMfaCode": "Verifying code...",
"sendMfaCodeError": "Sorry, we couldn't send you a verification code",
"verifyMfaCodeSuccess": "Code verified! Signing you in...",
"verifyMfaCodeError": "Ops! It looks like the code is not correct",
"reauthenticate": "Reauthenticate",
"reauthenticateDescription": "For security reasons, we need you to re-authenticate",
"errorAlertHeading": "Sorry, we could not authenticate you",
"emailConfirmationAlertHeading": "We sent you a confirmation email.",
"emailConfirmationAlertBody": "Welcome! Please check your email and click the link to verify your account.",
"resendLink": "Resend link",
"resendLinkSuccessDescription": "We sent you a new link to your email! Follow the link to sign in.",
"resendLinkSuccess": "Check your email!",
"authenticationErrorAlertHeading": "Authentication Error",
"authenticationErrorAlertBody": "Sorry, we could not authenticate you. Please try again.",
"sendEmailCode": "Get code to your Email",
"sendingEmailCode": "Sending code...",
"resetPasswordError": "Sorry, we could not reset your password. Please try again.",
"emailPlaceholder": "your@email.com",
"inviteAlertHeading": "You have been invited to join a team",
"inviteAlertBody": "Please sign in or sign up to accept the invite and join the team.",
"acceptTermsAndConditions": "I accept the <TermsOfServiceLink /> and <PrivacyPolicyLink />",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"orContinueWith": "Or continue with",
"redirecting": "You're in! Please wait...",
"errors": {
"Invalid login credentials": "The credentials entered are invalid",
"User already registered": "This credential is already in use. Please try with another one.",
"Email not confirmed": "Please confirm your email address before signing in",
"default": "We have encountered an error. Please ensure you have a working internet connection and try again",
"generic": "Sorry, we weren't able to authenticate you. Please try again.",
"link": "Sorry, we encountered an error while sending your link. Please try again.",
"codeVerifierMismatch": "It looks like you're trying to sign in using a different browser than the one you used to request the sign in link. Please try again using the same browser.",
"minPasswordLength": "Password must be at least 8 characters long",
"passwordsDoNotMatch": "The passwords do not match",
"minPasswordNumbers": "Password must contain at least one number",
"minPasswordSpecialChars": "Password must contain at least one special character",
"uppercasePassword": "Password must contain at least one uppercase letter",
"insufficient_aal": "Please sign-in with your current multi-factor authentication to perform this action",
"otp_expired": "The email link has expired. Please try again.",
"same_password": "The password cannot be the same as the current password"
}
}

View File

@@ -0,0 +1,120 @@
{
"subscriptionTabSubheading": "Manage your Subscription and Billing",
"planCardTitle": "Your Plan",
"planCardDescription": "Below are the details of your current plan. You can change your plan or cancel your subscription at any time.",
"planRenewal": "Renews every {{interval}} at {{price}}",
"planDetails": "Plan Details",
"checkout": "Proceed to Checkout",
"trialEndsOn": "Your trial ends on",
"billingPortalCardButton": "Visit Billing Portal",
"billingPortalCardTitle": "Manage your Billing Details",
"billingPortalCardDescription": "Visit your Billing Portal to manage your subscription and billing. You can update or cancel your plan, or download your invoices.",
"cancelAtPeriodEndDescription": "Your subscription is scheduled to be canceled on {{- endDate }}.",
"renewAtPeriodEndDescription": "Your subscription is scheduled to be renewed on {{- endDate }}",
"noPermissionsAlertHeading": "You don't have permissions to change the billing settings",
"noPermissionsAlertBody": "Please contact your account owner to change the billing settings for your account.",
"checkoutSuccessTitle": "Done! You're all set.",
"checkoutSuccessDescription": "Thank you for subscribing, we have successfully processed your subscription! A confirmation email will be sent to {{ customerEmail }}.",
"checkoutSuccessBackButton": "Proceed to App",
"cannotManageBillingAlertTitle": "You cannot manage billing",
"cannotManageBillingAlertDescription": "You do not have permissions to manage billing. Please contact your account owner.",
"manageTeamPlan": "Manage your Team Plan",
"manageTeamPlanDescription": "Choose a plan that fits your team's needs. You can upgrade or downgrade your plan at any time.",
"basePlan": "Base Plan",
"billingInterval": {
"label": "Choose your billing interval",
"month": "Billed monthly",
"year": "Billed yearly"
},
"perMonth": "month",
"custom": "Custom Plan",
"lifetime": "Lifetime",
"trialPeriod": "{{period}} day trial",
"perPeriod": "per {{period}}",
"redirectingToPayment": "Redirecting to checkout. Please wait...",
"proceedToPayment": "Proceed to Payment",
"startTrial": "Start Trial",
"perTeamMember": "Per team member",
"perUnit": "Per {{unit}} usage",
"teamMembers": "Team Members",
"includedUpTo": "Up to {{upTo}} {{unit}} included in the plan",
"fromPreviousTierUpTo": "for each {{unit}} for the next {{ upTo }} {{ unit }}",
"andAbove": "above {{ previousTier }} {{ unit }}",
"startingAtPriceUnit": "Starting at {{price}}/{{unit}}",
"priceUnit": "{{price}}/{{unit}}",
"forEveryUnit": "for every {{ unit }}",
"setupFee": "plus a {{ setupFee }} setup fee",
"perUnitIncluded": "({{included}} included)",
"featuresLabel": "Features",
"detailsLabel": "Details",
"planPickerLabel": "Pick your preferred plan",
"planCardLabel": "Manage your Plan",
"planPickerAlertErrorTitle": "Error requesting checkout",
"planPickerAlertErrorDescription": "There was an error requesting checkout. Please try again later.",
"subscriptionCancelled": "Subscription Cancelled",
"cancelSubscriptionDate": "Your subscription will be cancelled at the end of the period",
"noPlanChosen": "Please choose a plan",
"noIntervalPlanChosen": "Please choose a billing interval",
"status": {
"free": {
"badge": "Free Plan",
"heading": "You are currently on the Free Plan",
"description": "You're on a free plan. You can upgrade to a paid plan at any time."
},
"active": {
"badge": "Active",
"heading": "Your subscription is active",
"description": "Your subscription is active. You can manage your subscription and billing in the Customer Portal."
},
"trialing": {
"badge": "Trial",
"heading": "You're on a trial",
"description": "You can enjoy the benefits of plan until the trial ends"
},
"past_due": {
"badge": "Past Due",
"heading": "Your invoice is past due",
"description": "Your invoice is past due. Please update your payment method."
},
"canceled": {
"badge": "Canceled",
"heading": "Your subscription is canceled",
"description": "Your subscription is canceled. It is scheduled to end at end of the billing period."
},
"unpaid": {
"badge": "Unpaid",
"heading": "Your invoice is unpaid",
"description": "Your invoice is unpaid. Please update your payment method."
},
"incomplete": {
"badge": "Incomplete",
"heading": "We're waiting for your payment",
"description": "We're waiting for your payment to go through. Please bear with us."
},
"incomplete_expired": {
"badge": "Expired",
"heading": "Your payment has expired",
"description": "Your payment has expired. Please update your payment method."
},
"paused": {
"badge": "Paused",
"heading": "Your subscription is paused",
"description": "Your subscription is paused. You can resume it at any time."
},
"succeeded": {
"badge": "Succeeded",
"heading": "Your payment was successful",
"description": "Your payment was successful. Thank you for subscribing!"
},
"pending": {
"badge": "Pending",
"heading": "Your payment is pending",
"description": "Your payment is pending. Please bear with us."
},
"failed": {
"badge": "Failed",
"heading": "Your payment failed",
"description": "Your payment failed. Please update your payment method."
}
}
}

View File

@@ -0,0 +1,96 @@
{
"homeTabLabel": "Home",
"homeTabDescription": "Welcome to your home page",
"accountMembers": "Team Members",
"membersTabDescription": "Here you can manage the members of your team.",
"billingTabLabel": "Billing",
"billingTabDescription": "Manage your billing and subscription",
"dashboardTabLabel": "Dashboard",
"settingsTabLabel": "Settings",
"profileSettingsTabLabel": "Profile",
"subscriptionSettingsTabLabel": "Subscription",
"dashboardTabDescription": "An overview of your account's activity and performance across all your projects.",
"settingsTabDescription": "Manage your settings and preferences.",
"emailAddress": "Email Address",
"password": "Password",
"modalConfirmationQuestion": "Are you sure you want to continue?",
"imageInputLabel": "Click here to upload an image",
"cancel": "Cancel",
"clear": "Clear",
"notFound": "Not Found",
"backToHomePage": "Back to Home Page",
"goBack": "Go Back",
"genericServerError": "Sorry, something went wrong.",
"genericServerErrorHeading": "Sorry, something went wrong while processing your request. Please contact us if the issue persists.",
"pageNotFound": "Sorry, this page does not exist.",
"pageNotFoundSubHeading": "Apologies, the page you were looking for was not found",
"genericError": "Sorry, something went wrong.",
"genericErrorSubHeading": "Apologies, an error occurred while processing your request. Please contact us if the issue persists.",
"anonymousUser": "Anonymous",
"tryAgain": "Try Again",
"theme": "Theme",
"lightTheme": "Light",
"darkTheme": "Dark",
"systemTheme": "System",
"expandSidebar": "Expand Sidebar",
"collapseSidebar": "Collapse Sidebar",
"documentation": "Documentation",
"getStarted": "Get Started",
"getStartedWithPlan": "Get Started with {{plan}}",
"retry": "Retry",
"contactUs": "Contact Us",
"loading": "Loading. Please wait...",
"yourAccounts": "Your Accounts",
"continue": "Continue",
"skip": "Skip",
"signedInAs": "Signed in as",
"pageOfPages": "Page {{page}} of {{total}}",
"noData": "No data available",
"pageNotFoundHeading": "Ouch! :|",
"errorPageHeading": "Ouch! :|",
"notifications": "Notifications",
"noNotifications": "No notifications",
"justNow": "Just now",
"newVersionAvailable": "New version available",
"newVersionAvailableDescription": "A new version of the app is available. It is recommended to refresh the page to get the latest updates and avoid any issues.",
"newVersionSubmitButton": "Reload and Update",
"back": "Back",
"routes": {
"home": "Home",
"account": "Account",
"members": "Members",
"billing": "Billing",
"dashboard": "Dashboard",
"settings": "Settings",
"profile": "Profile",
"application": "Application"
},
"roles": {
"owner": {
"label": "Owner"
},
"member": {
"label": "Member"
}
},
"otp": {
"requestVerificationCode": "Request Verification Code",
"requestVerificationCodeDescription": "We must verify your identity to continue with this action. We'll send a verification code to the email address {{email}}.",
"sendingCode": "Sending Code...",
"sendVerificationCode": "Send Verification Code",
"enterVerificationCode": "Enter Verification Code",
"codeSentToEmail": "We've sent a verification code to the email address {{email}}.",
"verificationCode": "Verification Code",
"enterCodeFromEmail": "Enter the 6-digit code we sent to your email.",
"verifying": "Verifying...",
"verifyCode": "Verify Code",
"requestNewCode": "Request New Code",
"errorSendingCode": "Error sending code. Please try again."
},
"cookieBanner": {
"title": "Hey, we use cookies 🍪",
"description": "This website uses cookies to ensure you get the best experience on our website.",
"reject": "Reject",
"accept": "Accept"
}
}

View File

@@ -0,0 +1,40 @@
{
"blog": "Blog",
"blogSubtitle": "News and updates about the platform",
"documentation": "Documentation",
"documentationSubtitle": "Tutorials and guide to get started with the platform",
"faq": "FAQ",
"faqSubtitle": "Frequently asked questions about the platform",
"pricing": "Pricing",
"pricingSubtitle": "Pricing plans and payment options",
"backToBlog": "Back to blog",
"noPosts": "No posts found",
"blogPaginationNext": "Next Page",
"blogPaginationPrevious": "Previous Page",
"readMore": "Read more",
"contactFaq": "If you have any questions, please contact us",
"contact": "Contact",
"about": "About",
"product": "Product",
"legal": "Legal",
"termsOfService": "Terms of Service",
"termsOfServiceDescription": "Our terms and conditions",
"cookiePolicy": "Cookie Policy",
"cookiePolicyDescription": "Our cookie policy and how we use them",
"privacyPolicy": "Privacy Policy",
"privacyPolicyDescription": "Our privacy policy and how we use your data",
"contactDescription": "Contact us for any questions or feedback",
"contactHeading": "Send us a message",
"contactSubheading": "We will get back to you as soon as possible",
"contactName": "Your Name",
"contactEmail": "Your Email",
"contactMessage": "Your Message",
"sendMessage": "Send Message",
"contactSuccess": "Your message has been sent successfully",
"contactError": "An error occurred while sending your message",
"contactSuccessDescription": "We have received your message and will get back to you as soon as possible",
"contactErrorDescription": "An error occurred while sending your message. Please try again later",
"footerDescription": "Here you can add a description about your company or product",
"copyright": "© Copyright {{year}} {{product}}. All Rights Reserved.",
"heroSubtitle": "Простой, удобный и быстрый обзор вашего состояния здоровья"
}

View File

@@ -0,0 +1,163 @@
{
"home": {
"pageTitle": "Home"
},
"settings": {
"pageTitle": "Settings",
"pageDescription": "Manage your Team details",
"teamLogo": "Team Logo",
"teamLogoDescription": "Update your team's logo to make it easier to identify",
"teamName": "Team Name",
"teamNameDescription": "Update your team's name",
"dangerZone": "Danger Zone",
"dangerZoneDescription": "This section contains actions that are irreversible"
},
"members": {
"pageTitle": "Members"
},
"billing": {
"pageTitle": "Billing"
},
"yourTeams": "Your Teams ({{teamsCount}})",
"createTeam": "Create a Team",
"creatingTeam": "Creating Team...",
"personalAccount": "Personal Account",
"searchAccount": "Search Account...",
"membersTabLabel": "Members",
"memberName": "Name",
"youLabel": "You",
"emailLabel": "Email",
"roleLabel": "Role",
"primaryOwnerLabel": "Primary Owner",
"joinedAtLabel": "Joined at",
"invitedAtLabel": "Invited at",
"inviteMembersPageSubheading": "Invite members to your Team",
"createTeamModalHeading": "Create Team",
"createTeamModalDescription": "Create a new Team to manage your projects and members.",
"teamNameLabel": "Team Name",
"teamNameDescription": "Your team name should be unique and descriptive",
"createTeamSubmitLabel": "Create Team",
"createTeamSuccess": "Team created successfully",
"createTeamError": "Team not created. Please try again.",
"createTeamLoading": "Creating team...",
"settingsPageLabel": "General",
"createTeamDropdownLabel": "New team",
"changeRole": "Change Role",
"removeMember": "Remove from Account",
"inviteMembersSuccess": "Members invited successfully!",
"inviteMembersError": "Sorry, we encountered an error! Please try again",
"inviteMembersLoading": "Inviting members...",
"removeInviteButtonLabel": "Remove invite",
"addAnotherMemberButtonLabel": "Add another one",
"inviteMembersButtonLabel": "Send Invites",
"removeMemberModalHeading": "You are removing this user",
"removeMemberModalDescription": "Remove this member from the team. They will no longer have access to the team.",
"removeMemberSuccessMessage": "Member removed successfully",
"removeMemberErrorMessage": "Sorry, we encountered an error. Please try again",
"removeMemberErrorHeading": "Sorry, we couldn't remove the selected member.",
"removeMemberLoadingMessage": "Removing member...",
"removeMemberSubmitLabel": "Remove User from Team",
"chooseDifferentRoleError": "Role is the same as the current one",
"updateRole": "Update Role",
"updateRoleLoadingMessage": "Updating role...",
"updateRoleSuccessMessage": "Role updated successfully",
"updatingRoleErrorMessage": "Sorry, we encountered an error. Please try again.",
"updateMemberRoleModalHeading": "Update Member's Role",
"updateMemberRoleModalDescription": "Change the role of the selected member. The role determines the permissions of the member.",
"roleMustBeDifferent": "Role must be different from the current one",
"memberRoleInputLabel": "Member role",
"updateRoleDescription": "Pick a role for this member.",
"updateRoleSubmitLabel": "Update Role",
"transferOwnership": "Transfer Ownership",
"transferOwnershipDescription": "Transfer ownership of the team to another member.",
"transferOwnershipInputLabel": "Please type TRANSFER to confirm the transfer of ownership.",
"transferOwnershipInputDescription": "By transferring ownership, you will no longer be the primary owner of the team.",
"deleteInvitation": "Delete Invitation",
"deleteInvitationDialogDescription": "You are about to delete the invitation. The user will no longer be able to join the team.",
"deleteInviteSuccessMessage": "Invite deleted successfully",
"deleteInviteErrorMessage": "Invite not deleted. Please try again.",
"deleteInviteLoadingMessage": "Deleting invite. Please wait...",
"confirmDeletingMemberInvite": "You are deleting the invite to <b>{{ email }}</b>",
"transferOwnershipDisclaimer": "You are transferring ownership of the selected team to <b>{{ member }}</b>.",
"transferringOwnership": "Transferring ownership...",
"transferOwnershipSuccess": "Ownership successfully transferred",
"transferOwnershipError": "Sorry, we could not transfer ownership to the selected member. Please try again.",
"deleteInviteSubmitLabel": "Delete Invite",
"youBadgeLabel": "You",
"updateTeamLoadingMessage": "Updating Team...",
"updateTeamSuccessMessage": "Team successfully updated",
"updateTeamErrorMessage": "Could not update Team. Please try again.",
"updateLogoErrorMessage": "Could not update Logo. Please try again.",
"teamNameInputLabel": "Team Name",
"teamLogoInputHeading": "Upload your team's Logo",
"teamLogoInputSubheading": "Please choose a photo to upload as your team logo.",
"updateTeamSubmitLabel": "Update Team",
"inviteMembersHeading": "Invite Members to your Team",
"inviteMembersDescription": "Invite members to your team by entering their email and role.",
"emailPlaceholder": "member@email.com",
"membersPageHeading": "Members",
"inviteMembersButton": "Invite Members",
"invitingMembers": "Inviting members...",
"inviteMembersSuccessMessage": "Members invited successfully",
"inviteMembersErrorMessage": "Sorry, members could not be invited. Please try again.",
"pendingInvitesHeading": "Pending Invites",
"pendingInvitesDescription": " Here you can manage the pending invitations to your team.",
"noPendingInvites": "No pending invites found",
"loadingMembers": "Loading members...",
"loadMembersError": "Sorry, we couldn't fetch your team's members.",
"loadInvitedMembersError": "Sorry, we couldn't fetch your team's invited members.",
"loadingInvitedMembers": "Loading invited members...",
"invitedBadge": "Invited",
"duplicateInviteEmailError": "You have already entered this email address",
"invitingOwnAccountError": "Hey, that's your email!",
"dangerZone": "Danger Zone",
"dangerZoneSubheading": "Delete or leave your team",
"deleteTeam": "Delete Team",
"deleteTeamDescription": "This action cannot be undone. All data associated with this team will be deleted.",
"deletingTeam": "Deleting team",
"deleteTeamModalHeading": "Deleting Team",
"deletingTeamDescription": "You are about to delete the team {{ teamName }}. This action cannot be undone.",
"deleteTeamInputField": "Type the name of the team to confirm",
"leaveTeam": "Leave Team",
"leavingTeamModalHeading": "Leaving Team",
"leavingTeamModalDescription": "You are about to leave this team. You will no longer have access to it.",
"leaveTeamDescription": "Click the button below to leave the team. Remember, you will no longer have access to it and will need to be re-invited to join.",
"deleteTeamDisclaimer": "You are deleting the team {{ teamName }}. This action cannot be undone.",
"leaveTeamDisclaimer": "You are leaving the team {{ teamName }}. You will no longer have access to it.",
"deleteTeamErrorHeading": "Sorry, we couldn't delete your team.",
"leaveTeamErrorHeading": "Sorry, we couldn't leave your team.",
"searchMembersPlaceholder": "Search members",
"createTeamErrorHeading": "Sorry, we couldn't create your team.",
"createTeamErrorMessage": "We encountered an error creating your team. Please try again.",
"transferTeamErrorHeading": "Sorry, we couldn't transfer ownership of your team.",
"transferTeamErrorMessage": "We encountered an error transferring ownership of your team. Please try again.",
"updateRoleErrorHeading": "Sorry, we couldn't update the role of the selected member.",
"updateRoleErrorMessage": "We encountered an error updating the role of the selected member. Please try again.",
"searchInvitations": "Search Invitations",
"updateInvitation": "Update Invitation",
"removeInvitation": "Remove Invitation",
"acceptInvitation": "Accept Invitation",
"renewInvitation": "Renew Invitation",
"resendInvitation": "Resend Invitation",
"expiresAtLabel": "Expires at",
"expired": "Expired",
"active": "Active",
"inviteStatus": "Status",
"inviteNotFoundOrExpired": "Invite not found or expired",
"inviteNotFoundOrExpiredDescription": "The invite you are looking for is either expired or does not exist. Please contact the team owner to renew the invite.",
"backToHome": "Back to Home",
"renewInvitationDialogDescription": "You are about to renew the invitation to {{ email }}. The user will be able to join the team.",
"renewInvitationErrorTitle": "Sorry, we couldn't renew the invitation.",
"renewInvitationErrorDescription": "We encountered an error renewing the invitation. Please try again.",
"signInWithDifferentAccount": "Sign in with a different account",
"signInWithDifferentAccountDescription": "If you wish to accept the invitation with a different account, please sign out and back in with the account you wish to use.",
"acceptInvitationHeading": "Accept Invitation to join {{accountName}}",
"acceptInvitationDescription": "You have been invited to join the team {{accountName}}. If you wish to accept the invitation, please click the button below.",
"continueAs": "Continue as {{email}}",
"joinTeamAccount": "Join Team",
"joiningTeam": "Joining team...",
"leaveTeamInputLabel": "Please type LEAVE to confirm leaving the team.",
"leaveTeamInputDescription": "By leaving the team, you will no longer have access to it.",
"reservedNameError": "This name is reserved. Please choose a different one.",
"specialCharactersError": "This name cannot contain special characters. Please choose a different one."
}

View File

@@ -32,6 +32,7 @@
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
/* @apply font-sans */
}
*,
@@ -46,4 +47,10 @@
textarea::placeholder {
color: theme(--color-muted-foreground);
}
h1,h2,h3,h4,h5,h6 {
@apply font-heading text-foreground text-2xl font-semibold tracking-tight
}
}

View File

@@ -8,38 +8,39 @@
@layer base {
:root {
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-heading: var(--font-sans);
--font-sans: var(--font-sans) -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-heading: var(--font-heading);
--background: var(--color-white);
--foreground: var(--color-neutral-950);
--foreground: hsla(240, 10%, 4%, 1);
--card: var(--color-white);
--card-foreground: var(--color-neutral-950);
--popover: var(--color-white);
--popover-foreground: var(--color-neutral-950);
--popover-foreground: hsla(240, 10%, 4%, 1);
--primary: var(--color-neutral-950);
--primary-foreground: var(--color-white);
--primary: hsla(145, 78%, 18%, 1);
--primary-foreground: hsla(356, 100%, 97%, 1);
--secondary: oklch(96.76% 0.0013 286.38);
--secondary-foreground: oklch(21.03% 0.0318 264.65);
--secondary: hsla(240, 5%, 96%, 1);
--secondary-foreground: hsla(240, 6%, 10%, 1);
--muted: oklch(96.71% 0.0029 264.54);
--muted-foreground: oklch(55.13% 0.0233 264.36);
--muted: hsla(240, 5%, 96%, 1);
--muted-foreground: hsla(240, 4%, 41%, 1);
--accent: oklch(96.76% 0.0013 286.38);
--accent-foreground: oklch(21.03% 0.0318 264.65);
--accent: hsla(240, 5%, 96%, 1);
--accent-foreground: hsla(240, 6%, 10%, 1);
--destructive: var(--color-red-500);
--destructive-foreground: var(--color-white);
--destructive: hsla(0, 84%, 60%, 1);
--destructive-foreground: hsla(0, 0%, 98%, 1);
--border: var(--color-gray-100);
--input: var(--color-gray-200);
--border: hsla(240, 6%, 90%, 1);
--input: hsla(240, 6%, 90%, 1);
--ring: var(--color-neutral-800);
--radius: 0.5rem;
--radius: 1rem;
--chart-1: var(--color-orange-400);
--chart-2: var(--color-teal-600);
@@ -55,6 +56,7 @@
--sidebar-accent-foreground: var(--color-neutral-950);
--sidebar-border: var(--border);
--sidebar-ring: var(--color-blue-500);
/* --foreground: 240 10% 4%; */
}
.dark {
@@ -74,7 +76,7 @@
--secondary-foreground: oklch(98.43% 0.0017 247.84);
--muted: var(--color-neutral-800);
--muted-foreground: oklch(71.19% 0.0129 286.07);
--muted-foreground: hsla(240, 4%, 41%, 1);
--accent: var(--color-neutral-800);
--accent-foreground: oklch(98.48% 0 0);

View File

@@ -66,6 +66,16 @@
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--breakpoint-xs: 30rem;
--breakpoint-sm: 48rem;
--breakpoint-md: 70rem;
--breakpoint-lg: 80rem;
--breakpoint-xl: 96rem;
--breakpoint-2xl: 100rem;
--breakpoint-3xl: 120rem;
--container-max-width: 80rem;
@keyframes accordion-down {
from {
height: 0;

View File

@@ -10,7 +10,7 @@ enabled = true
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. `public` and `graphql_public` schemas are included by default.
schemas = ["public", "graphql_public"]
schemas = ["public", "graphql_public", "audit"]
# Extra schemas to add to the search_path of every request.
extra_search_path = ["public", "extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size

1285
supabase/database.types.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,206 @@
create table "public"."analyses" (
"id" bigint generated by default as identity not null,
"analysis_id_oid" text not null,
"analysis_id_original" text not null,
"tehik_short_loinc" text,
"tehik_loinc_name" text,
"analysis_name_lab" text,
"order" smallint,
"created_at" timestamp with time zone not null default now(),
"updated_at" timestamp with time zone default now(),
"parent_analysis_element_id" bigint not null
);
alter table "public"."analyses" enable row level security;
create table "public"."analysis_elements" (
"id" bigint generated by default as identity not null,
"analysis_id_oid" text,
"analysis_id_original" text not null,
"tehik_short_loinc" text,
"tehik_loinc_name" text,
"analysis_name_lab" text,
"order" smallint,
"created_at" timestamp with time zone not null default now(),
"updated_at" timestamp with time zone default now(),
"parent_analysis_group_id" bigint not null,
"material_groups" jsonb[]
);
alter table "public"."analysis_elements" enable row level security;
create table "public"."analysis_groups" (
"id" bigint generated by default as identity not null,
"original_id" text not null,
"name" text,
"order" smallint,
"created_at" timestamp with time zone not null default now(),
"updated_at" timestamp with time zone default now()
);
alter table "public"."analysis_groups" enable row level security;
create table "public"."codes" (
"id" bigint generated by default as identity not null,
"hk_code" text not null,
"hk_code_multiplier" bigint not null,
"coefficient" double precision not null,
"price" double precision not null,
"analysis_group_id" bigint,
"analysis_element_id" bigint,
"analysis_id" bigint,
"updated_at" timestamp with time zone default now(),
"created_at" timestamp with time zone not null default now()
);
alter table "public"."codes" enable row level security;
CREATE UNIQUE INDEX analysis_elements_pkey ON public.analysis_elements USING btree (id);
CREATE UNIQUE INDEX analysis_elements_original_id_key ON public.analysis_elements USING btree (analysis_id_original);
CREATE UNIQUE INDEX analysis_group_original_id_key ON public.analysis_groups USING btree (original_id);
CREATE UNIQUE INDEX analysis_group_pkey ON public.analysis_groups USING btree (id);
CREATE UNIQUE INDEX analysis_pkey ON public.analyses USING btree (id);
CREATE UNIQUE INDEX analysis_original_id_key ON public.analysis_elements USING btree (analysis_id_original);
CREATE UNIQUE INDEX codes_pkey ON public.codes USING btree (id);
CREATE UNIQUE INDEX analyses_analysis_id_original_key ON public.analyses USING btree (analysis_id_original);
CREATE UNIQUE INDEX analysis_elements_analysis_id_original_key ON public.analysis_elements USING btree (analysis_id_original);
alter table "public"."analyses" add constraint "analyses_analysis_id_original_key" UNIQUE using index "analyses_analysis_id_original_key";
alter table "public"."analysis_elements" add constraint "analysis_elements_analysis_id_original_key" UNIQUE using index "analysis_elements_analysis_id_original_key";
alter table "public"."analyses" add constraint "analysis_pkey" PRIMARY KEY using index "analysis_pkey";
alter table "public"."analysis_elements" add constraint "analysis_elements_pkey" PRIMARY KEY using index "analysis_elements_pkey";
alter table "public"."analysis_groups" add constraint "analysis_group_pkey" PRIMARY KEY using index "analysis_group_pkey";
alter table "public"."codes" add constraint "codes_pkey" PRIMARY KEY using index "codes_pkey";
alter table "public"."analyses" add constraint "analyses_parent_analysis_element_id_fkey" FOREIGN KEY (parent_analysis_element_id) REFERENCES analysis_elements(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
alter table "public"."analyses" validate constraint "analyses_parent_analysis_element_id_fkey";
alter table "public"."analysis_elements" add constraint "analysis_elements_parent_analysis_group_id_fkey" FOREIGN KEY (parent_analysis_group_id) REFERENCES analysis_groups(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
alter table "public"."analysis_elements" validate constraint "analysis_elements_parent_analysis_group_id_fkey";
alter table "public"."analysis_groups" add constraint "analysis_group_original_id_key" UNIQUE using index "analysis_group_original_id_key";
alter table "public"."codes" add constraint "codes_analysis_element_id_fkey" FOREIGN KEY (analysis_element_id) REFERENCES analysis_elements(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
alter table "public"."codes" validate constraint "codes_analysis_element_id_fkey";
alter table "public"."codes" add constraint "codes_analysis_group_id_fkey" FOREIGN KEY (analysis_group_id) REFERENCES analysis_groups(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
alter table "public"."codes" validate constraint "codes_analysis_group_id_fkey";
alter table "public"."codes" add constraint "codes_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
alter table "public"."codes" validate constraint "codes_analysis_id_fkey";
grant delete on table "public"."analyses" to "service_role";
grant insert on table "public"."analyses" to "service_role";
grant references on table "public"."analyses" to "service_role";
grant select on table "public"."analyses" to "service_role";
grant trigger on table "public"."analyses" to "service_role";
grant truncate on table "public"."analyses" to "service_role";
grant update on table "public"."analyses" to "service_role";
grant delete on table "public"."analysis_elements" to "service_role";
grant insert on table "public"."analysis_elements" to "service_role";
grant references on table "public"."analysis_elements" to "service_role";
grant select on table "public"."analysis_elements" to "service_role";
grant trigger on table "public"."analysis_elements" to "service_role";
grant truncate on table "public"."analysis_elements" to "service_role";
grant update on table "public"."analysis_elements" to "service_role";
grant delete on table "public"."analysis_groups" to "service_role";
grant insert on table "public"."analysis_groups" to "service_role";
grant references on table "public"."analysis_groups" to "service_role";
grant select on table "public"."analysis_groups" to "service_role";
grant trigger on table "public"."analysis_groups" to "service_role";
grant truncate on table "public"."analysis_groups" to "service_role";
grant update on table "public"."analysis_groups" to "service_role";
grant delete on table "public"."codes" to "service_role";
grant insert on table "public"."codes" to "service_role";
grant references on table "public"."codes" to "service_role";
grant select on table "public"."codes" to "service_role";
grant trigger on table "public"."codes" to "service_role";
grant truncate on table "public"."codes" to "service_role";
grant update on table "public"."codes" to "service_role";
create policy "analysis_all"
on "public"."analyses"
as permissive
for all
to service_role
using (true);
create policy "analysis_elements_all"
on "public"."analysis_elements"
as permissive
for all
to service_role
using (true);
create policy "analysis_groups_all"
on "public"."analysis_groups"
as permissive
for all
to service_role
using (true);
create policy "codes_all"
on "public"."codes"
as permissive
for all
to service_role
using (true);
CREATE TRIGGER analysis_change_record_timestamps AFTER INSERT OR DELETE OR UPDATE ON public.analyses FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();
CREATE TRIGGER analysis_elements_change_record_timestamps AFTER INSERT OR DELETE OR UPDATE ON public.analysis_elements FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();
CREATE TRIGGER analysis_groups_change_record_timestamps AFTER INSERT OR DELETE OR UPDATE ON public.analysis_groups FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();
CREATE TRIGGER codes_change_record_timestamps AFTER INSERT OR DELETE OR UPDATE ON public.codes FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();

View File

@@ -0,0 +1,87 @@
create schema if not exists audit;
create table if not exists audit.log_entries (
"id" bigint generated by default as identity not null,
"schema_name" text not null,
"table_name" text not null,
"record_key" bigint,
"operation" text not null,
"row_data" jsonb,
"changed_data" jsonb,
"changed_by" uuid,
"changed_by_role" text,
"changed_at" timestamptz not null default now()
);
alter table "audit"."log_entries" enable row level security;
create policy "service_role_all"
on "audit"."log_entries"
as permissive
for all
to service_role
using (true);
create or replace function audit.log_audit_changes()
returns trigger
language plpgsql
as $$
declare
current_user_id uuid;
current_user_role text;
begin
begin
current_user_id := auth.uid();
current_user_role := auth.jwt() ->> 'role';
end;
insert into audit.log_entries (
schema_name,
table_name,
record_key,
operation,
row_data,
changed_data,
changed_by,
changed_by_role
)
values (
tg_table_schema,
tg_table_name,
case when tg_op in ('DELETE', 'UPDATE') then old.id else null end,
tg_op,
case when tg_op in ('DELETE', 'UPDATE') then to_jsonb(old) else null end,
case when tg_op in ('INSERT', 'UPDATE') then to_jsonb(new) else null end,
current_user_id,
current_user_role
);
return null;
end;
$$;
create table "audit"."sync_entries" (
"id" bigint generated by default as identity not null,
"status" text not null,
"operation" text not null,
"comment" text,
"created_at" timestamp with time zone not null default now(),
"changed_by_role" text not null
);
create type "audit"."sync_status" as enum ('SUCCESS', 'FAIL');
alter table "audit"."sync_entries" enable row level security;
CREATE UNIQUE INDEX sync_entries_pkey ON audit.sync_entries USING btree (id);
alter table "audit"."sync_entries" add constraint "sync_entries_pkey" PRIMARY KEY using index "sync_entries_pkey";
create policy "service_role_all"
on "audit"."sync_entries"
as permissive
for all
to public
using (true);
GRANT USAGE ON SCHEMA audit TO service_role;
GRANT ALL ON ALL TABLES IN SCHEMA audit TO service_role;

View File

@@ -1,80 +0,0 @@
import type { Config } from "tailwindcss";
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;
export default config;