diff --git a/.env.example b/.env.example index 2cd4fdc..b35abfa 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx index ac7b2ed..8826673 100644 --- a/app/(marketing)/page.tsx +++ b/app/(marketing)/page.tsx @@ -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 (
- Med Report - - } + title={} subtitle={ - Lihtne, mugav ja kiire ülevaade Sinu tervise seisundist + } cta={} @@ -56,8 +53,8 @@ function MainCallToActionButton() { - - + +
diff --git a/app/(public)/layout.tsx b/app/(public)/layout.tsx new file mode 100644 index 0000000..b6f3825 --- /dev/null +++ b/app/(public)/layout.tsx @@ -0,0 +1,11 @@ +import { withI18n } from '~/lib/i18n/with-i18n'; + +function SiteLayout(props: React.PropsWithChildren) { + return ( +
+ {props.children} +
+ ); +} + +export default withI18n(SiteLayout); diff --git a/app/(public)/register-company/page.tsx b/app/(public)/register-company/page.tsx new file mode 100644 index 0000000..e3e54e7 --- /dev/null +++ b/app/(public)/register-company/page.tsx @@ -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 ( +
+
+ +

Ettevõtte andmed

+

+ Pakkumise saamiseks palun sisesta ettevõtte andmed millega MedReport + kasutada kavatsed. +

+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ ); +} diff --git a/app/(public)/register-company/success/page.tsx b/app/(public)/register-company/success/page.tsx new file mode 100644 index 0000000..688dbb0 --- /dev/null +++ b/app/(public)/register-company/success/page.tsx @@ -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 ( +
+ +
+ Success +

Päring edukalt saadetud!

+

Saadame teile esimesel võimalusel vastuse

+
+ +
+ ); +} diff --git a/app/(public)/sign-in/page.tsx b/app/(public)/sign-in/page.tsx new file mode 100644 index 0000000..aca9ead --- /dev/null +++ b/app/(public)/sign-in/page.tsx @@ -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 ( +
+ + + + +
+ ); +} diff --git a/app/icon.ico b/app/icon.ico new file mode 100644 index 0000000..ad7ea1f Binary files /dev/null and b/app/icon.ico differ diff --git a/components/MedReportTitle.tsx b/components/MedReportTitle.tsx new file mode 100644 index 0000000..a010e11 --- /dev/null +++ b/components/MedReportTitle.tsx @@ -0,0 +1,10 @@ +import { MedReportSmallLogo } from "@/public/assets/MedReportSmallLogo"; + +export const MedReportTitle = () => ( +
+ + + MedReport + +
+); diff --git a/components/header-auth.tsx b/components/header-auth.tsx new file mode 100644 index 0000000..aab2ec7 --- /dev/null +++ b/components/header-auth.tsx @@ -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 ( + <> +
+
+ + Please update .env.local file with anon key and url + +
+
+ + +
+
+ + ); + } + return user ? ( +
+ Hey, {user.email}! +
+ +
+
+ ) : ( +
+ +
+ ); +} diff --git a/components/ui/submit-button.tsx b/components/ui/submit-button.tsx new file mode 100644 index 0000000..93cf1a1 --- /dev/null +++ b/components/ui/submit-button.tsx @@ -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 & { + pendingText?: string; +}; + +export function SubmitButton({ + children, + pendingText = "Submitting...", + ...props +}: Props) { + const { pending } = useFormStatus(); + + return ( + + ); +} \ No newline at end of file diff --git a/fonts/InterDisplay-Medium.woff2 b/fonts/InterDisplay-Medium.woff2 new file mode 100644 index 0000000..f6157fa Binary files /dev/null and b/fonts/InterDisplay-Medium.woff2 differ diff --git a/fonts/InterDisplay-Regular.woff2 b/fonts/InterDisplay-Regular.woff2 new file mode 100644 index 0000000..b5a45e8 Binary files /dev/null and b/fonts/InterDisplay-Regular.woff2 differ diff --git a/lib/actions/sign-out.tsx b/lib/actions/sign-out.tsx new file mode 100644 index 0000000..0137a93 --- /dev/null +++ b/lib/actions/sign-out.tsx @@ -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"); +}; diff --git a/lib/fonts.ts b/lib/fonts.ts index bdf7195..a9ec3df 100644 --- a/lib/fonts.ts +++ b/lib/fonts.ts @@ -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 }; diff --git a/lib/i18n/i18n.settings.ts b/lib/i18n/i18n.settings.ts index 4be9cf3..bb70925 100644 --- a/lib/i18n/i18n.settings.ts +++ b/lib/i18n/i18n.settings.ts @@ -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. diff --git a/lib/services/medipost.service.ts b/lib/services/medipost.service.ts index 703241f..f6f1b9c 100644 --- a/lib/services/medipost.service.ts +++ b/lib/services/medipost.service.ts @@ -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. 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>[] = 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; diff --git a/lib/services/register-company.service.ts b/lib/services/register-company.service.ts new file mode 100644 index 0000000..c030462 --- /dev/null +++ b/lib/services/register-company.service.ts @@ -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; + } +} diff --git a/lib/types/audit.ts b/lib/types/audit.ts new file mode 100644 index 0000000..f8eed24 --- /dev/null +++ b/lib/types/audit.ts @@ -0,0 +1,4 @@ +export enum SyncStatus { + Success = "SUCCESS", + Fail = "FAIL", +} diff --git a/lib/types/company.ts b/lib/types/company.ts new file mode 100644 index 0000000..7fbc4d6 --- /dev/null +++ b/lib/types/company.ts @@ -0,0 +1,6 @@ +export interface CompanySubmitData { + companyName: string; + contactPerson: string; + email: string; + phone?: string; +} diff --git a/lib/types/medipost.ts b/lib/types/medipost.ts index 7f94da1..cdb5931 100644 --- a/lib/types/medipost.ts +++ b/lib/types/medipost.ts @@ -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 + }; + }; +}; diff --git a/lib/utils.ts b/lib/utils.ts index a5ef193..405dd01 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -4,3 +4,8 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function toArray(input?: T | T[] | null): T[] { + if (!input) return []; + return Array.isArray(input) ? input : [input]; +} diff --git a/lib/validations/companySchema.ts b/lib/validations/companySchema.ts new file mode 100644 index 0000000..b265f81 --- /dev/null +++ b/lib/validations/companySchema.ts @@ -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(), +}); diff --git a/package.json b/package.json index 43a3321..82a583a 100644 --- a/package.json +++ b/package.json @@ -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" ] -} +} \ No newline at end of file diff --git a/packages/ui/src/makerkit/marketing/header.tsx b/packages/ui/src/makerkit/marketing/header.tsx index fcba508..1489ef2 100644 --- a/packages/ui/src/makerkit/marketing/header.tsx +++ b/packages/ui/src/makerkit/marketing/header.tsx @@ -1,5 +1,5 @@ import { cn } from '../../lib/utils'; - +import { LanguageSelector } from '@kit/ui/language-selector'; interface HeaderProps extends React.HTMLAttributes { logo?: React.ReactNode; navigation?: React.ReactNode; @@ -25,7 +25,8 @@ export const Header: React.FC = function ({
{logo}
{navigation}
-
{actions}
+ +
{actions}
diff --git a/packages/ui/src/shadcn/button.tsx b/packages/ui/src/shadcn/button.tsx index 85ee15c..6210a54 100644 --- a/packages/ui/src/shadcn/button.tsx +++ b/packages/ui/src/shadcn/button.tsx @@ -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: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cc46d5..e84ce96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/public/assets/MedReportSmallLogo.tsx b/public/assets/MedReportSmallLogo.tsx new file mode 100644 index 0000000..8d5ec33 --- /dev/null +++ b/public/assets/MedReportSmallLogo.tsx @@ -0,0 +1,16 @@ +export const MedReportSmallLogo = () => { + return ( + + + + ); +}; diff --git a/public/assets/med-report-logo-big.png b/public/assets/med-report-logo-big.png new file mode 100644 index 0000000..50f9e72 Binary files /dev/null and b/public/assets/med-report-logo-big.png differ diff --git a/public/assets/success.png b/public/assets/success.png new file mode 100644 index 0000000..9947cbb Binary files /dev/null and b/public/assets/success.png differ diff --git a/public/locales/en/account.json b/public/locales/en/account.json index fd8a54a..28ee8d9 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -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" } diff --git a/public/locales/en/marketing.json b/public/locales/en/marketing.json index 1325b5a..a214fc2 100644 --- a/public/locales/en/marketing.json +++ b/public/locales/en/marketing.json @@ -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" } diff --git a/public/locales/et/account.json b/public/locales/et/account.json new file mode 100644 index 0000000..3526791 --- /dev/null +++ b/public/locales/et/account.json @@ -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" +} diff --git a/public/locales/et/auth.json b/public/locales/et/auth.json new file mode 100644 index 0000000..3bb81d4 --- /dev/null +++ b/public/locales/et/auth.json @@ -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 {{email}}", + "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 and ", + "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" + } +} diff --git a/public/locales/et/billing.json b/public/locales/et/billing.json new file mode 100644 index 0000000..9efef35 --- /dev/null +++ b/public/locales/et/billing.json @@ -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." + } + } +} diff --git a/public/locales/et/common.json b/public/locales/et/common.json new file mode 100644 index 0000000..c194ce3 --- /dev/null +++ b/public/locales/et/common.json @@ -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" + } +} diff --git a/public/locales/et/marketing.json b/public/locales/et/marketing.json new file mode 100644 index 0000000..96420bd --- /dev/null +++ b/public/locales/et/marketing.json @@ -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" +} diff --git a/public/locales/et/teams.json b/public/locales/et/teams.json new file mode 100644 index 0000000..2a0b614 --- /dev/null +++ b/public/locales/et/teams.json @@ -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 {{ email }}", + "transferOwnershipDisclaimer": "You are transferring ownership of the selected team to {{ member }}.", + "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." +} diff --git a/public/locales/ru/account.json b/public/locales/ru/account.json new file mode 100644 index 0000000..28ee8d9 --- /dev/null +++ b/public/locales/ru/account.json @@ -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" +} diff --git a/public/locales/ru/auth.json b/public/locales/ru/auth.json new file mode 100644 index 0000000..3bb81d4 --- /dev/null +++ b/public/locales/ru/auth.json @@ -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 {{email}}", + "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 and ", + "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" + } +} diff --git a/public/locales/ru/billing.json b/public/locales/ru/billing.json new file mode 100644 index 0000000..9efef35 --- /dev/null +++ b/public/locales/ru/billing.json @@ -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." + } + } +} diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json new file mode 100644 index 0000000..c194ce3 --- /dev/null +++ b/public/locales/ru/common.json @@ -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" + } +} diff --git a/public/locales/ru/marketing.json b/public/locales/ru/marketing.json new file mode 100644 index 0000000..50076c7 --- /dev/null +++ b/public/locales/ru/marketing.json @@ -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": "Простой, удобный и быстрый обзор вашего состояния здоровья" +} diff --git a/public/locales/ru/teams.json b/public/locales/ru/teams.json new file mode 100644 index 0000000..2a0b614 --- /dev/null +++ b/public/locales/ru/teams.json @@ -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 {{ email }}", + "transferOwnershipDisclaimer": "You are transferring ownership of the selected team to {{ member }}.", + "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." +} diff --git a/styles/globals.css b/styles/globals.css index 4b1d646..2ee21cd 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -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 + } + } \ No newline at end of file diff --git a/styles/shadcn-ui.css b/styles/shadcn-ui.css index 731f388..86319f8 100644 --- a/styles/shadcn-ui.css +++ b/styles/shadcn-ui.css @@ -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); diff --git a/styles/theme.css b/styles/theme.css index 2ac6017..df2155d 100644 --- a/styles/theme.css +++ b/styles/theme.css @@ -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; diff --git a/supabase/config.toml b/supabase/config.toml index 2ab0d40..4483207 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -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 diff --git a/supabase/database.types.ts b/supabase/database.types.ts new file mode 100644 index 0000000..bb836be --- /dev/null +++ b/supabase/database.types.ts @@ -0,0 +1,1285 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + audit: { + Tables: { + log_entries: { + Row: { + changed_at: string + changed_by: string | null + changed_by_role: string | null + changed_data: Json | null + id: number + operation: string + record_key: number | null + row_data: Json | null + schema_name: string + table_name: string + } + Insert: { + changed_at?: string + changed_by?: string | null + changed_by_role?: string | null + changed_data?: Json | null + id?: number + operation: string + record_key?: number | null + row_data?: Json | null + schema_name: string + table_name: string + } + Update: { + changed_at?: string + changed_by?: string | null + changed_by_role?: string | null + changed_data?: Json | null + id?: number + operation?: string + record_key?: number | null + row_data?: Json | null + schema_name?: string + table_name?: string + } + Relationships: [] + } + sync_entries: { + Row: { + changed_by_role: string | null + created_at: string + id: number + operation: string | null + status: string + } + Insert: { + changed_by_role?: string | null + created_at?: string + id?: number + operation?: string | null + status: string + } + Update: { + changed_by_role?: string | null + created_at?: string + id?: number + operation?: string | null + status?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + sync_status: "SUCCESS" | "FAIL" + } + CompositeTypes: { + [_ in never]: never + } + } + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + accounts: { + Row: { + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + public_data: Json + slug: string | null + updated_at: string | null + updated_by: string | null + } + Insert: { + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name: string + picture_url?: string | null + primary_owner_user_id?: string + public_data?: Json + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } + Update: { + created_at?: string | null + created_by?: string | null + email?: string | null + id?: string + is_personal_account?: boolean + name?: string + picture_url?: string | null + primary_owner_user_id?: string + public_data?: Json + slug?: string | null + updated_at?: string | null + updated_by?: string | null + } + Relationships: [] + } + accounts_memberships: { + Row: { + account_id: string + account_role: string + created_at: string + created_by: string | null + updated_at: string + updated_by: string | null + user_id: string + } + Insert: { + account_id: string + account_role: string + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id: string + } + Update: { + account_id?: string + account_role?: string + created_at?: string + created_by?: string | null + updated_at?: string + updated_by?: string | null + user_id?: string + } + Relationships: [ + { + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "accounts_memberships_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "accounts_memberships_account_role_fkey" + columns: ["account_role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] + }, + ] + } + analyses: { + Row: { + analysis_id_oid: string + analysis_id_original: string + analysis_name_lab: string | null + created_at: string + id: number + order: number | null + parent_analysis_element_id: number + tehik_loinc_name: string | null + tehik_short_loinc: string | null + updated_at: string | null + } + Insert: { + analysis_id_oid: string + analysis_id_original: string + analysis_name_lab?: string | null + created_at?: string + id?: number + order?: number | null + parent_analysis_element_id: number + tehik_loinc_name?: string | null + tehik_short_loinc?: string | null + updated_at?: string | null + } + Update: { + analysis_id_oid?: string + analysis_id_original?: string + analysis_name_lab?: string | null + created_at?: string + id?: number + order?: number | null + parent_analysis_element_id?: number + tehik_loinc_name?: string | null + tehik_short_loinc?: string | null + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "analyses_parent_analysis_element_id_fkey" + columns: ["parent_analysis_element_id"] + isOneToOne: false + referencedRelation: "analysis_elements" + referencedColumns: ["id"] + }, + ] + } + analysis_elements: { + Row: { + analysis_id_oid: string | null + analysis_id_original: string + analysis_name_lab: string | null + created_at: string + id: number + material_groups: Json[] | null + order: number | null + parent_analysis_group_id: number + tehik_loinc_name: string | null + tehik_short_loinc: string | null + updated_at: string | null + } + Insert: { + analysis_id_oid?: string | null + analysis_id_original: string + analysis_name_lab?: string | null + created_at?: string + id?: number + material_groups?: Json[] | null + order?: number | null + parent_analysis_group_id: number + tehik_loinc_name?: string | null + tehik_short_loinc?: string | null + updated_at?: string | null + } + Update: { + analysis_id_oid?: string | null + analysis_id_original?: string + analysis_name_lab?: string | null + created_at?: string + id?: number + material_groups?: Json[] | null + order?: number | null + parent_analysis_group_id?: number + tehik_loinc_name?: string | null + tehik_short_loinc?: string | null + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "analysis_elements_parent_analysis_group_id_fkey" + columns: ["parent_analysis_group_id"] + isOneToOne: false + referencedRelation: "analysis_groups" + referencedColumns: ["id"] + }, + ] + } + analysis_groups: { + Row: { + created_at: string + id: number + name: string | null + order: number | null + original_id: string + updated_at: string | null + } + Insert: { + created_at?: string + id?: number + name?: string | null + order?: number | null + original_id: string + updated_at?: string | null + } + Update: { + created_at?: string + id?: number + name?: string | null + order?: number | null + original_id?: string + updated_at?: string | null + } + Relationships: [] + } + billing_customers: { + Row: { + account_id: string + customer_id: string + email: string | null + id: number + provider: Database["public"]["Enums"]["billing_provider"] + } + Insert: { + account_id: string + customer_id: string + email?: string | null + id?: number + provider: Database["public"]["Enums"]["billing_provider"] + } + Update: { + account_id?: string + customer_id?: string + email?: string | null + id?: number + provider?: Database["public"]["Enums"]["billing_provider"] + } + Relationships: [ + { + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "billing_customers_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + ] + } + codes: { + Row: { + analysis_element_id: number | null + analysis_group_id: number | null + analysis_id: number | null + coefficient: number + created_at: string + hk_code: string + hk_code_multiplier: number + id: number + price: number + updated_at: string | null + } + Insert: { + analysis_element_id?: number | null + analysis_group_id?: number | null + analysis_id?: number | null + coefficient: number + created_at?: string + hk_code: string + hk_code_multiplier: number + id?: number + price: number + updated_at?: string | null + } + Update: { + analysis_element_id?: number | null + analysis_group_id?: number | null + analysis_id?: number | null + coefficient?: number + created_at?: string + hk_code?: string + hk_code_multiplier?: number + id?: number + price?: number + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "codes_analysis_element_id_fkey" + columns: ["analysis_element_id"] + isOneToOne: false + referencedRelation: "analysis_elements" + referencedColumns: ["id"] + }, + { + foreignKeyName: "codes_analysis_group_id_fkey" + columns: ["analysis_group_id"] + isOneToOne: false + referencedRelation: "analysis_groups" + referencedColumns: ["id"] + }, + { + foreignKeyName: "codes_analysis_id_fkey" + columns: ["analysis_id"] + isOneToOne: false + referencedRelation: "analyses" + referencedColumns: ["id"] + }, + ] + } + config: { + Row: { + billing_provider: Database["public"]["Enums"]["billing_provider"] + enable_account_billing: boolean + enable_team_account_billing: boolean + enable_team_accounts: boolean + } + Insert: { + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_team_account_billing?: boolean + enable_team_accounts?: boolean + } + Update: { + billing_provider?: Database["public"]["Enums"]["billing_provider"] + enable_account_billing?: boolean + enable_team_account_billing?: boolean + enable_team_accounts?: boolean + } + Relationships: [] + } + invitations: { + Row: { + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: string + updated_at: string + } + Insert: { + account_id: string + created_at?: string + email: string + expires_at?: string + id?: number + invite_token: string + invited_by: string + role: string + updated_at?: string + } + Update: { + account_id?: string + created_at?: string + email?: string + expires_at?: string + id?: number + invite_token?: string + invited_by?: string + role?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "invitations_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "invitations_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] + }, + ] + } + notifications: { + Row: { + account_id: string + body: string + channel: Database["public"]["Enums"]["notification_channel"] + created_at: string + dismissed: boolean + expires_at: string | null + id: number + link: string | null + type: Database["public"]["Enums"]["notification_type"] + } + Insert: { + account_id: string + body: string + channel?: Database["public"]["Enums"]["notification_channel"] + created_at?: string + dismissed?: boolean + expires_at?: string | null + id?: never + link?: string | null + type?: Database["public"]["Enums"]["notification_type"] + } + Update: { + account_id?: string + body?: string + channel?: Database["public"]["Enums"]["notification_channel"] + created_at?: string + dismissed?: boolean + expires_at?: string | null + id?: never + link?: string | null + type?: Database["public"]["Enums"]["notification_type"] + } + Relationships: [ + { + foreignKeyName: "notifications_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "notifications_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "notifications_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + ] + } + order_items: { + Row: { + created_at: string + id: string + order_id: string + price_amount: number | null + product_id: string + quantity: number + updated_at: string + variant_id: string + } + Insert: { + created_at?: string + id: string + order_id: string + price_amount?: number | null + product_id: string + quantity?: number + updated_at?: string + variant_id: string + } + Update: { + created_at?: string + id?: string + order_id?: string + price_amount?: number | null + product_id?: string + quantity?: number + updated_at?: string + variant_id?: string + } + Relationships: [ + { + foreignKeyName: "order_items_order_id_fkey" + columns: ["order_id"] + isOneToOne: false + referencedRelation: "orders" + referencedColumns: ["id"] + }, + ] + } + orders: { + Row: { + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at: string + } + Insert: { + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at?: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at?: string + } + Update: { + account_id?: string + billing_customer_id?: number + billing_provider?: Database["public"]["Enums"]["billing_provider"] + created_at?: string + currency?: string + id?: string + status?: Database["public"]["Enums"]["payment_status"] + total_amount?: number + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "orders_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "orders_billing_customer_id_fkey" + columns: ["billing_customer_id"] + isOneToOne: false + referencedRelation: "billing_customers" + referencedColumns: ["id"] + }, + ] + } + role_permissions: { + Row: { + id: number + permission: Database["public"]["Enums"]["app_permissions"] + role: string + } + Insert: { + id?: number + permission: Database["public"]["Enums"]["app_permissions"] + role: string + } + Update: { + id?: number + permission?: Database["public"]["Enums"]["app_permissions"] + role?: string + } + Relationships: [ + { + foreignKeyName: "role_permissions_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] + }, + ] + } + roles: { + Row: { + hierarchy_level: number + name: string + } + Insert: { + hierarchy_level: number + name: string + } + Update: { + hierarchy_level?: number + name?: string + } + Relationships: [] + } + subscription_items: { + Row: { + created_at: string + id: string + interval: string + interval_count: number + price_amount: number | null + product_id: string + quantity: number + subscription_id: string + type: Database["public"]["Enums"]["subscription_item_type"] + updated_at: string + variant_id: string + } + Insert: { + created_at?: string + id: string + interval: string + interval_count: number + price_amount?: number | null + product_id: string + quantity?: number + subscription_id: string + type: Database["public"]["Enums"]["subscription_item_type"] + updated_at?: string + variant_id: string + } + Update: { + created_at?: string + id?: string + interval?: string + interval_count?: number + price_amount?: number | null + product_id?: string + quantity?: number + subscription_id?: string + type?: Database["public"]["Enums"]["subscription_item_type"] + updated_at?: string + variant_id?: string + } + Relationships: [ + { + foreignKeyName: "subscription_items_subscription_id_fkey" + columns: ["subscription_id"] + isOneToOne: false + referencedRelation: "subscriptions" + referencedColumns: ["id"] + }, + ] + } + subscriptions: { + Row: { + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + } + Insert: { + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at?: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + } + Update: { + account_id?: string + active?: boolean + billing_customer_id?: number + billing_provider?: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end?: boolean + created_at?: string + currency?: string + id?: string + period_ends_at?: string + period_starts_at?: string + status?: Database["public"]["Enums"]["subscription_status"] + trial_ends_at?: string | null + trial_starts_at?: string | null + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_account_workspace" + referencedColumns: ["id"] + }, + { + foreignKeyName: "subscriptions_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "user_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "subscriptions_billing_customer_id_fkey" + columns: ["billing_customer_id"] + isOneToOne: false + referencedRelation: "billing_customers" + referencedColumns: ["id"] + }, + ] + } + } + Views: { + user_account_workspace: { + Row: { + id: string | null + name: string | null + picture_url: string | null + subscription_status: + | Database["public"]["Enums"]["subscription_status"] + | null + } + Relationships: [] + } + user_accounts: { + Row: { + id: string | null + name: string | null + picture_url: string | null + role: string | null + slug: string | null + } + Relationships: [ + { + foreignKeyName: "accounts_memberships_account_role_fkey" + columns: ["role"] + isOneToOne: false + referencedRelation: "roles" + referencedColumns: ["name"] + }, + ] + } + } + Functions: { + accept_invitation: { + Args: { token: string; user_id: string } + Returns: string + } + add_invitations_to_account: { + Args: { + account_slug: string + invitations: Database["public"]["CompositeTypes"]["invitation"][] + } + Returns: Database["public"]["Tables"]["invitations"]["Row"][] + } + can_action_account_member: { + Args: { target_team_account_id: string; target_user_id: string } + Returns: boolean + } + create_invitation: { + Args: { account_id: string; email: string; role: string } + Returns: { + account_id: string + created_at: string + email: string + expires_at: string + id: number + invite_token: string + invited_by: string + role: string + updated_at: string + } + } + create_team_account: { + Args: { account_name: string } + Returns: { + created_at: string | null + created_by: string | null + email: string | null + id: string + is_personal_account: boolean + name: string + picture_url: string | null + primary_owner_user_id: string + public_data: Json + slug: string | null + updated_at: string | null + updated_by: string | null + } + } + get_account_invitations: { + Args: { account_slug: string } + Returns: { + id: number + email: string + account_id: string + invited_by: string + role: string + created_at: string + updated_at: string + expires_at: string + inviter_name: string + inviter_email: string + }[] + } + get_account_members: { + Args: { account_slug: string } + Returns: { + id: string + user_id: string + account_id: string + role: string + role_hierarchy_level: number + primary_owner_user_id: string + name: string + email: string + picture_url: string + created_at: string + updated_at: string + }[] + } + get_config: { + Args: Record + Returns: Json + } + get_upper_system_role: { + Args: Record + Returns: string + } + has_active_subscription: { + Args: { target_account_id: string } + Returns: boolean + } + has_more_elevated_role: { + Args: { + target_user_id: string + target_account_id: string + role_name: string + } + Returns: boolean + } + has_permission: { + Args: { + user_id: string + account_id: string + permission_name: Database["public"]["Enums"]["app_permissions"] + } + Returns: boolean + } + has_role_on_account: { + Args: { account_id: string; account_role?: string } + Returns: boolean + } + has_same_role_hierarchy_level: { + Args: { + target_user_id: string + target_account_id: string + role_name: string + } + Returns: boolean + } + is_account_owner: { + Args: { account_id: string } + Returns: boolean + } + is_account_team_member: { + Args: { target_account_id: string } + Returns: boolean + } + is_set: { + Args: { field_name: string } + Returns: boolean + } + is_team_member: { + Args: { account_id: string; user_id: string } + Returns: boolean + } + team_account_workspace: { + Args: { account_slug: string } + Returns: { + id: string + name: string + picture_url: string + slug: string + role: string + role_hierarchy_level: number + primary_owner_user_id: string + subscription_status: Database["public"]["Enums"]["subscription_status"] + permissions: Database["public"]["Enums"]["app_permissions"][] + }[] + } + transfer_team_account_ownership: { + Args: { target_account_id: string; new_owner_id: string } + Returns: undefined + } + upsert_order: { + Args: { + target_account_id: string + target_customer_id: string + target_order_id: string + status: Database["public"]["Enums"]["payment_status"] + billing_provider: Database["public"]["Enums"]["billing_provider"] + total_amount: number + currency: string + line_items: Json + } + Returns: { + account_id: string + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + created_at: string + currency: string + id: string + status: Database["public"]["Enums"]["payment_status"] + total_amount: number + updated_at: string + } + } + upsert_subscription: { + Args: { + target_account_id: string + target_customer_id: string + target_subscription_id: string + active: boolean + status: Database["public"]["Enums"]["subscription_status"] + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + currency: string + period_starts_at: string + period_ends_at: string + line_items: Json + trial_starts_at?: string + trial_ends_at?: string + } + Returns: { + account_id: string + active: boolean + billing_customer_id: number + billing_provider: Database["public"]["Enums"]["billing_provider"] + cancel_at_period_end: boolean + created_at: string + currency: string + id: string + period_ends_at: string + period_starts_at: string + status: Database["public"]["Enums"]["subscription_status"] + trial_ends_at: string | null + trial_starts_at: string | null + updated_at: string + } + } + } + Enums: { + app_permissions: + | "roles.manage" + | "billing.manage" + | "settings.manage" + | "members.manage" + | "invites.manage" + billing_provider: "stripe" | "lemon-squeezy" | "paddle" + notification_channel: "in_app" | "email" + notification_type: "info" | "warning" | "error" + payment_status: "pending" | "succeeded" | "failed" + subscription_item_type: "flat" | "per_seat" | "metered" + subscription_status: + | "active" + | "trialing" + | "past_due" + | "canceled" + | "unpaid" + | "incomplete" + | "incomplete_expired" + | "paused" + } + CompositeTypes: { + invitation: { + email: string | null + role: string | null + } + } + } +} + +type DefaultSchema = Database[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + audit: { + Enums: { + sync_status: ["SUCCESS", "FAIL"], + }, + }, + graphql_public: { + Enums: {}, + }, + public: { + Enums: { + app_permissions: [ + "roles.manage", + "billing.manage", + "settings.manage", + "members.manage", + "invites.manage", + ], + billing_provider: ["stripe", "lemon-squeezy", "paddle"], + notification_channel: ["in_app", "email"], + notification_type: ["info", "warning", "error"], + payment_status: ["pending", "succeeded", "failed"], + subscription_item_type: ["flat", "per_seat", "metered"], + subscription_status: [ + "active", + "trialing", + "past_due", + "canceled", + "unpaid", + "incomplete", + "incomplete_expired", + "paused", + ], + }, + }, +} as const + diff --git a/supabase/migrations/20250602164242_create_medipost_tables.sql b/supabase/migrations/20250602164242_create_medipost_tables.sql new file mode 100644 index 0000000..ef2ba38 --- /dev/null +++ b/supabase/migrations/20250602164242_create_medipost_tables.sql @@ -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(); + diff --git a/supabase/migrations/20250604115051_add_audit_schema.sql b/supabase/migrations/20250604115051_add_audit_schema.sql new file mode 100644 index 0000000..e4a489b --- /dev/null +++ b/supabase/migrations/20250604115051_add_audit_schema.sql @@ -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; diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index 41668a3..0000000 --- a/tailwind.config.ts +++ /dev/null @@ -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;