@@ -343,6 +346,10 @@ function FactorQrCode({
+
+
+ {form.getValues('totpSecret')}
+
);
}
diff --git a/packages/features/admin/src/lib/server/schema/create-company.schema.ts b/packages/features/admin/src/lib/server/schema/create-company.schema.ts
index 42ef6cb..c2d33cf 100644
--- a/packages/features/admin/src/lib/server/schema/create-company.schema.ts
+++ b/packages/features/admin/src/lib/server/schema/create-company.schema.ts
@@ -10,7 +10,7 @@ const personalCodeSchema = z.string().refine(
}
},
{
- message: 'Invalid personal code',
+ message: 'common:formFieldError.invalidPersonalCode',
},
);
diff --git a/packages/features/auth/src/components/auth-layout.tsx b/packages/features/auth/src/components/auth-layout.tsx
index 2d83e61..003da16 100644
--- a/packages/features/auth/src/components/auth-layout.tsx
+++ b/packages/features/auth/src/components/auth-layout.tsx
@@ -7,9 +7,8 @@ export function AuthLayoutShell({
return (
{Logo ?
: null}
diff --git a/packages/features/auth/src/components/oauth-providers.tsx b/packages/features/auth/src/components/oauth-providers.tsx
index 11dcc94..2bb7830 100644
--- a/packages/features/auth/src/components/oauth-providers.tsx
+++ b/packages/features/auth/src/components/oauth-providers.tsx
@@ -113,12 +113,16 @@ export const OauthProviders: React.FC<{
);
}}
>
-
+ {provider === 'keycloak' ? (
+
+ ) : (
+
+ )}
);
})}
diff --git a/packages/features/auth/src/components/password-sign-up-container.tsx b/packages/features/auth/src/components/password-sign-up-container.tsx
index 5cbe21a..631c7a5 100644
--- a/packages/features/auth/src/components/password-sign-up-container.tsx
+++ b/packages/features/auth/src/components/password-sign-up-container.tsx
@@ -10,9 +10,18 @@ import { useCaptchaToken } from '../captcha/client';
import { usePasswordSignUpFlow } from '../hooks/use-sign-up-flow';
import { AuthErrorAlert } from './auth-error-alert';
import { PasswordSignUpForm } from './password-sign-up-form';
+import { Spinner } from '@kit/ui/makerkit/spinner';
interface EmailPasswordSignUpContainerProps {
- displayTermsCheckbox?: boolean;
+ authConfig: {
+ providers: {
+ password: boolean;
+ magicLink: boolean;
+ oAuth: string[];
+ };
+ displayTermsCheckbox: boolean | undefined;
+ isMailerAutoconfirmEnabled: boolean;
+ };
defaultValues?: {
email: string;
};
@@ -21,10 +30,10 @@ interface EmailPasswordSignUpContainerProps {
}
export function EmailPasswordSignUpContainer({
+ authConfig,
defaultValues,
onSignUp,
emailRedirectTo,
- displayTermsCheckbox,
}: EmailPasswordSignUpContainerProps) {
const { captchaToken, resetCaptchaToken } = useCaptchaToken();
@@ -43,7 +52,12 @@ export function EmailPasswordSignUpContainer({
return (
<>
-
+ {authConfig.isMailerAutoconfirmEnabled ? (
+
+
+
+ ) :
+ }
@@ -53,7 +67,7 @@ export function EmailPasswordSignUpContainer({
onSubmit={onSignupRequested}
loading={loading}
defaultValues={defaultValues}
- displayTermsCheckbox={displayTermsCheckbox}
+ displayTermsCheckbox={authConfig.displayTermsCheckbox}
/>
>
diff --git a/packages/features/auth/src/components/sign-in-methods-container.tsx b/packages/features/auth/src/components/sign-in-methods-container.tsx
index 344c040..6b1e8c7 100644
--- a/packages/features/auth/src/components/sign-in-methods-container.tsx
+++ b/packages/features/auth/src/components/sign-in-methods-container.tsx
@@ -15,6 +15,12 @@ import { MagicLinkAuthContainer } from './magic-link-auth-container';
import { OauthProviders } from './oauth-providers';
import { PasswordSignInContainer } from './password-sign-in-container';
+export type Providers = {
+ password: boolean;
+ magicLink: boolean;
+ oAuth: Provider[];
+};
+
export function SignInMethodsContainer(props: {
inviteToken?: string;
@@ -25,11 +31,7 @@ export function SignInMethodsContainer(props: {
updateAccount: string;
};
- providers: {
- password: boolean;
- magicLink: boolean;
- oAuth: Provider[];
- };
+ providers: Providers;
}) {
const client = useSupabase();
const router = useRouter();
diff --git a/packages/features/auth/src/components/sign-up-methods-container.tsx b/packages/features/auth/src/components/sign-up-methods-container.tsx
index aadbfb5..00782bd 100644
--- a/packages/features/auth/src/components/sign-up-methods-container.tsx
+++ b/packages/features/auth/src/components/sign-up-methods-container.tsx
@@ -1,8 +1,7 @@
'use client';
-import { redirect } from 'next/navigation';
-
import type { Provider } from '@supabase/supabase-js';
+import { useRouter } from 'next/navigation';
import { isBrowser } from '@kit/shared/utils';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
@@ -21,15 +20,20 @@ export function SignUpMethodsContainer(props: {
updateAccount: string;
};
- providers: {
- password: boolean;
- magicLink: boolean;
- oAuth: Provider[];
+ authConfig: {
+ providers: {
+ password: boolean;
+ magicLink: boolean;
+ oAuth: Provider[];
+ };
+ displayTermsCheckbox: boolean | undefined;
+ isMailerAutoconfirmEnabled: boolean;
};
- displayTermsCheckbox?: boolean;
inviteToken?: string;
}) {
+ const router = useRouter();
+
const redirectUrl = getCallbackUrl(props);
const defaultValues = getDefaultValues();
@@ -39,26 +43,33 @@ export function SignUpMethodsContainer(props: {
-
+
redirect(redirectUrl)}
+ authConfig={props.authConfig}
+ onSignUp={() => {
+ if (!props.authConfig.isMailerAutoconfirmEnabled) {
+ return;
+ }
+ setTimeout(() => {
+ router.replace(props.paths.updateAccount)
+ }, 2_500);
+ }}
/>
-
+
-
+
@@ -72,7 +83,7 @@ export function SignUpMethodsContainer(props: {
=> {
try {
const cookies = await nextCookies()
- const token = cookies.get("_medusa_jwt")?.value
+ const token = cookies.get(CookieName.MEDUSA_JWT)?.value
if (!token) {
return {}
@@ -23,7 +31,7 @@ export const getMedusaCustomerId = async (): Promise<
> => {
try {
const cookies = await nextCookies()
- const customerId = cookies.get("_medusa_customer_id")?.value
+ const customerId = cookies.get(CookieName.MEDUSA_CUSTOMER_ID)?.value
if (!customerId) {
return { customerId: null }
@@ -31,14 +39,14 @@ export const getMedusaCustomerId = async (): Promise<
return { customerId }
} catch {
- return { customerId: null}
+ return { customerId: null }
}
}
export const getCacheTag = async (tag: string): Promise => {
try {
const cookies = await nextCookies()
- const cacheId = cookies.get("_medusa_cache_id")?.value
+ const cacheId = cookies.get(CookieName.MEDUSA_CACHE_ID)?.value
if (!cacheId) {
return ""
@@ -66,51 +74,51 @@ export const getCacheOptions = async (
return { tags: [`${cacheTag}`] }
}
+const getCookieSharedOptions = () => ({
+ maxAge: 60 * 60 * 24 * 7,
+ httpOnly: false,
+ secure: process.env.NODE_ENV === "production",
+});
+const getCookieResetOptions = () => ({
+ maxAge: -1,
+});
+
export const setAuthToken = async (token: string) => {
const cookies = await nextCookies()
- cookies.set("_medusa_jwt", token, {
- maxAge: 60 * 60 * 24 * 7,
- httpOnly: true,
- sameSite: "strict",
- secure: process.env.NODE_ENV === "production",
+ cookies.set(CookieName.MEDUSA_JWT, token, {
+ ...getCookieSharedOptions(),
})
}
export const setMedusaCustomerId = async (customerId: string) => {
const cookies = await nextCookies()
- cookies.set("_medusa_customer_id", customerId, {
- maxAge: 60 * 60 * 24 * 7,
- httpOnly: true,
- sameSite: "strict",
- secure: process.env.NODE_ENV === "production",
+ cookies.set(CookieName.MEDUSA_CUSTOMER_ID, customerId, {
+ ...getCookieSharedOptions(),
})
}
export const removeAuthToken = async () => {
const cookies = await nextCookies()
- cookies.set("_medusa_jwt", "", {
- maxAge: -1,
+ cookies.set(CookieName.MEDUSA_JWT, "", {
+ ...getCookieResetOptions(),
})
}
export const getCartId = async () => {
const cookies = await nextCookies()
- return cookies.get("_medusa_cart_id")?.value
+ return cookies.get(CookieName.MEDUSA_CART_ID)?.value
}
export const setCartId = async (cartId: string) => {
const cookies = await nextCookies()
- cookies.set("_medusa_cart_id", cartId, {
- maxAge: 60 * 60 * 24 * 7,
- httpOnly: true,
- sameSite: "strict",
- secure: process.env.NODE_ENV === "production",
+ cookies.set(CookieName.MEDUSA_CART_ID, cartId, {
+ ...getCookieSharedOptions(),
})
}
export const removeCartId = async () => {
const cookies = await nextCookies()
- cookies.set("_medusa_cart_id", "", {
- maxAge: -1,
+ cookies.set(CookieName.MEDUSA_CART_ID, "", {
+ ...getCookieResetOptions(),
})
}
diff --git a/packages/features/medusa-storefront/src/lib/data/customer.ts b/packages/features/medusa-storefront/src/lib/data/customer.ts
index a33a33a..3c05921 100644
--- a/packages/features/medusa-storefront/src/lib/data/customer.ts
+++ b/packages/features/medusa-storefront/src/lib/data/customer.ts
@@ -126,18 +126,22 @@ export async function login(_currentState: unknown, formData: FormData) {
}
}
-export async function medusaLogout(countryCode = 'ee') {
+export async function medusaLogout(countryCode = 'ee', canRevalidateTags = true) {
await sdk.auth.logout()
await removeAuthToken()
- const customerCacheTag = await getCacheTag("customers")
- revalidateTag(customerCacheTag)
+ if (canRevalidateTags) {
+ const customerCacheTag = await getCacheTag("customers")
+ revalidateTag(customerCacheTag)
+ }
await removeCartId()
- const cartCacheTag = await getCacheTag("carts")
- revalidateTag(cartCacheTag)
+ if (canRevalidateTags) {
+ const cartCacheTag = await getCacheTag("carts")
+ revalidateTag(cartCacheTag)
+ }
}
export async function transferCart() {
diff --git a/packages/shared/src/components/ui/info-tooltip.tsx b/packages/shared/src/components/ui/info-tooltip.tsx
index 10a7ae3..1217c24 100644
--- a/packages/shared/src/components/ui/info-tooltip.tsx
+++ b/packages/shared/src/components/ui/info-tooltip.tsx
@@ -23,7 +23,7 @@ export function InfoTooltip({
{icon || }
- {content}
+ {content}
);
diff --git a/packages/shared/src/config/auth-providers.service.ts b/packages/shared/src/config/auth-providers.service.ts
new file mode 100644
index 0000000..179e7da
--- /dev/null
+++ b/packages/shared/src/config/auth-providers.service.ts
@@ -0,0 +1,144 @@
+import type { Provider } from '@supabase/supabase-js';
+import authConfig from './auth.config';
+
+type SupabaseExternalProvider = Provider | 'email';
+interface SupabaseAuthSettings {
+ external: Record;
+ disable_signup: boolean;
+ mailer_autoconfirm: boolean;
+}
+
+export class AuthProvidersService {
+ private supabaseUrl: string;
+ private cache: Map = new Map();
+ private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
+
+ constructor(supabaseUrl: string) {
+ this.supabaseUrl = supabaseUrl;
+ }
+
+ async fetchAuthSettings(): Promise {
+ try {
+ const cacheKey = 'auth-settings';
+ const cached = this.cache.get(cacheKey);
+
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
+ return cached.data;
+ }
+
+ const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
+ if (!anonKey) {
+ throw new Error('NEXT_PUBLIC_SUPABASE_ANON_KEY is required');
+ }
+
+ const response = await fetch(`${this.supabaseUrl}/auth/v1/settings?apikey=${anonKey}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ console.warn('Failed to fetch auth settings from Supabase:', response.status);
+ return null;
+ }
+
+ const settings: SupabaseAuthSettings = await response.json();
+
+ this.cache.set(cacheKey, { data: settings, timestamp: Date.now() });
+
+ return settings;
+ } catch (error) {
+ console.warn('Error fetching auth settings from Supabase:', error);
+ return null;
+ }
+ }
+
+ isPasswordEnabled({ settings }: { settings: SupabaseAuthSettings | null }): boolean {
+ if (settings) {
+ return settings.external.email === true && !settings.disable_signup;
+ }
+
+ return process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true';
+ }
+
+ isMailerAutoconfirmEnabled({ settings }: { settings: SupabaseAuthSettings | null }): boolean {
+ return settings?.mailer_autoconfirm === true;
+ }
+
+ isMagicLinkEnabled(): boolean {
+ return process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true';
+ }
+
+ isOAuthProviderEnabled({
+ provider,
+ settings,
+ }: {
+ provider: SupabaseExternalProvider;
+ settings: SupabaseAuthSettings | null;
+ }): boolean {
+ if (settings && settings.external) {
+ return settings.external[provider] === true;
+ }
+
+ return false;
+ }
+
+ getEnabledOAuthProviders({ settings }: { settings: SupabaseAuthSettings | null }): SupabaseExternalProvider[] {
+ const enabledProviders: SupabaseExternalProvider[] = [];
+
+ if (settings && settings.external) {
+ for (const [providerName, isEnabled] of Object.entries(settings.external)) {
+ if (isEnabled && providerName !== 'email') {
+ enabledProviders.push(providerName as SupabaseExternalProvider);
+ }
+ }
+ return enabledProviders;
+ }
+
+ const potentialProviders: SupabaseExternalProvider[] = ['keycloak'];
+ const enabledFallback: SupabaseExternalProvider[] = [];
+
+ for (const provider of potentialProviders) {
+ if (provider !== 'email' && this.isOAuthProviderEnabled({ provider, settings })) {
+ enabledFallback.push(provider);
+ }
+ }
+
+ return enabledFallback;
+ }
+
+ async getAuthConfig() {
+ const settings = await this.fetchAuthSettings();
+ const [passwordEnabled, magicLinkEnabled, oAuthProviders, isMailerAutoconfirmEnabled] = await Promise.all([
+ this.isPasswordEnabled({ settings }),
+ this.isMagicLinkEnabled(),
+ this.getEnabledOAuthProviders({ settings }),
+ this.isMailerAutoconfirmEnabled({ settings }),
+ ]);
+
+ return {
+ providers: {
+ password: passwordEnabled,
+ magicLink: magicLinkEnabled,
+ oAuth: oAuthProviders,
+ },
+ displayTermsCheckbox: authConfig.displayTermsCheckbox,
+ isMailerAutoconfirmEnabled,
+ };
+ }
+
+ clearCache(): void {
+ this.cache.clear();
+ }
+}
+
+export function createAuthProvidersService(): AuthProvidersService {
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
+
+ if (!supabaseUrl) {
+ throw new Error('NEXT_PUBLIC_SUPABASE_URL is required');
+ }
+
+ return new AuthProvidersService(supabaseUrl);
+}
diff --git a/packages/shared/src/config/dynamic-auth.config.ts b/packages/shared/src/config/dynamic-auth.config.ts
new file mode 100644
index 0000000..571516a
--- /dev/null
+++ b/packages/shared/src/config/dynamic-auth.config.ts
@@ -0,0 +1,114 @@
+import type { Provider } from '@supabase/supabase-js';
+import { z } from 'zod';
+import { createAuthProvidersService } from './auth-providers.service';
+
+const providers: z.ZodType = getProviders();
+
+const DynamicAuthConfigSchema = z.object({
+ providers: z.object({
+ password: z.boolean().describe('Enable password authentication.'),
+ magicLink: z.boolean().describe('Enable magic link authentication.'),
+ oAuth: providers.array(),
+ }),
+ displayTermsCheckbox: z.boolean().describe('Whether to display the terms checkbox during sign-up.'),
+ isMailerAutoconfirmEnabled: z.boolean().describe('Whether Supabase sends confirmation email automatically.'),
+});
+
+export type DynamicAuthConfig = {
+ providers: {
+ password: boolean;
+ magicLink: boolean;
+ oAuth: Provider[];
+ };
+ displayTermsCheckbox: boolean | undefined;
+ isMailerAutoconfirmEnabled: boolean;
+}
+
+export async function getDynamicAuthConfig() {
+ const authService = createAuthProvidersService();
+ const dynamicProviders = await authService.getAuthConfig();
+
+ const config = {
+ providers: dynamicProviders.providers,
+ displayTermsCheckbox: dynamicProviders.displayTermsCheckbox,
+ isMailerAutoconfirmEnabled: dynamicProviders.isMailerAutoconfirmEnabled,
+ };
+
+ return DynamicAuthConfigSchema.parse(config);
+}
+
+export async function getCachedAuthConfig() {
+ if (typeof window !== 'undefined') {
+ const cached = sessionStorage.getItem('auth-config');
+ if (cached) {
+ try {
+ const { data, timestamp } = JSON.parse(cached);
+ // Cache for 5 minutes
+ if (Date.now() - timestamp < 5 * 60 * 1000) {
+ return data;
+ }
+ } catch (error) {
+ console.warn('Invalid auth config cache:', error);
+ }
+ }
+ }
+
+ const config = await getDynamicAuthConfig();
+
+ if (typeof window !== 'undefined') {
+ try {
+ sessionStorage.setItem('auth-config', JSON.stringify({
+ data: config,
+ timestamp: Date.now(),
+ }));
+ } catch (error) {
+ console.warn('Failed to cache auth config:', error);
+ }
+ }
+
+ return config;
+}
+
+export async function getServerAuthConfig() {
+ return getDynamicAuthConfig();
+}
+
+export async function isProviderEnabled(provider: 'password' | 'magicLink' | Provider): Promise {
+ const authService = createAuthProvidersService();
+ const settings = await authService.fetchAuthSettings();
+
+ switch (provider) {
+ case 'password':
+ return authService.isPasswordEnabled({ settings });
+ case 'magicLink':
+ return authService.isMagicLinkEnabled();
+ default:
+ return authService.isOAuthProviderEnabled({ provider, settings });
+ }
+}
+
+function getProviders() {
+ return z.enum([
+ 'apple',
+ 'azure',
+ 'bitbucket',
+ 'discord',
+ 'facebook',
+ 'figma',
+ 'github',
+ 'gitlab',
+ 'google',
+ 'kakao',
+ 'keycloak',
+ 'linkedin',
+ 'linkedin_oidc',
+ 'notion',
+ 'slack',
+ 'spotify',
+ 'twitch',
+ 'twitter',
+ 'workos',
+ 'zoom',
+ 'fly',
+ ]);
+}
diff --git a/packages/shared/src/config/index.ts b/packages/shared/src/config/index.ts
index 516ecc7..d1737bb 100644
--- a/packages/shared/src/config/index.ts
+++ b/packages/shared/src/config/index.ts
@@ -8,6 +8,7 @@ import {
createPath,
getTeamAccountSidebarConfig,
} from './team-account-navigation.config';
+import { DynamicAuthConfig, getCachedAuthConfig, getServerAuthConfig } from './dynamic-auth.config';
export {
appConfig,
@@ -18,4 +19,7 @@ export {
getTeamAccountSidebarConfig,
pathsConfig,
personalAccountNavigationConfig,
+ getCachedAuthConfig,
+ getServerAuthConfig,
+ type DynamicAuthConfig,
};
diff --git a/packages/shared/src/hooks/index.ts b/packages/shared/src/hooks/index.ts
index 95e4bfd..b551263 100644
--- a/packages/shared/src/hooks/index.ts
+++ b/packages/shared/src/hooks/index.ts
@@ -1,2 +1,3 @@
export * from './use-csrf-token';
export * from './use-current-locale-language-names';
+export * from './use-auth-config';
diff --git a/packages/shared/src/hooks/use-auth-config.ts b/packages/shared/src/hooks/use-auth-config.ts
new file mode 100644
index 0000000..5282554
--- /dev/null
+++ b/packages/shared/src/hooks/use-auth-config.ts
@@ -0,0 +1,76 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import type { Provider } from '@supabase/supabase-js';
+import { getCachedAuthConfig } from '../config/dynamic-auth.config';
+import { authConfig } from '../config';
+
+interface AuthConfig {
+ providers: {
+ password: boolean;
+ magicLink: boolean;
+ oAuth: Provider[];
+ };
+}
+
+interface UseAuthConfigResult {
+ config: AuthConfig | null;
+ loading: boolean;
+ error: Error | null;
+ refetch: () => Promise;
+}
+
+export function useAuthConfig(): UseAuthConfigResult {
+ const [config, setConfig] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchConfig = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const authConfig = await getCachedAuthConfig();
+ setConfig(authConfig);
+ } catch (err) {
+ console.error('Failed to fetch auth config', err);
+ setError(err instanceof Error ? err : new Error('Failed to fetch auth config'));
+ setConfig(authConfig);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchConfig();
+ }, []);
+
+ return {
+ config,
+ loading,
+ error,
+ refetch: fetchConfig,
+ };
+}
+
+export function useProviderEnabled(provider: 'password' | 'magicLink' | Provider) {
+ const { config, loading, error } = useAuthConfig();
+
+ const isEnabled = (() => {
+ if (!config) return false;
+
+ switch (provider) {
+ case 'password':
+ return config.providers.password;
+ case 'magicLink':
+ return config.providers.magicLink;
+ default:
+ return config.providers.oAuth.includes(provider);
+ }
+ })();
+
+ return {
+ enabled: isEnabled,
+ loading,
+ error,
+ };
+}
diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts
index 971a03e..877cbca 100644
--- a/packages/shared/src/utils.ts
+++ b/packages/shared/src/utils.ts
@@ -1,5 +1,5 @@
import { format } from 'date-fns';
-import Isikukood, { Gender } from 'isikukood';
+import Isikukood from 'isikukood';
/**
* Check if the code is running in a browser environment.
diff --git a/packages/supabase/src/hooks/use-sign-out.ts b/packages/supabase/src/hooks/use-sign-out.ts
index c354cee..7a64bd3 100644
--- a/packages/supabase/src/hooks/use-sign-out.ts
+++ b/packages/supabase/src/hooks/use-sign-out.ts
@@ -10,7 +10,7 @@ export function useSignOut() {
try {
try {
const { medusaLogout } = await import('../../../features/medusa-storefront/src/lib/data/customer');
- await medusaLogout();
+ await medusaLogout(undefined, false);
} catch (medusaError) {
console.warn('Medusa logout failed or not available:', medusaError);
}
diff --git a/packages/supabase/src/hooks/use-user.ts b/packages/supabase/src/hooks/use-user.ts
index 0986775..9a9cdd9 100644
--- a/packages/supabase/src/hooks/use-user.ts
+++ b/packages/supabase/src/hooks/use-user.ts
@@ -28,8 +28,8 @@ export function useUser(initialData?: User | null) {
queryFn,
queryKey,
initialData,
- refetchInterval: false,
- refetchOnMount: false,
- refetchOnWindowFocus: false,
+ refetchInterval: 2_000,
+ refetchOnMount: true,
+ refetchOnWindowFocus: true,
});
}
diff --git a/packages/ui/src/makerkit/app-breadcrumbs.tsx b/packages/ui/src/makerkit/app-breadcrumbs.tsx
index 31296b2..dc38b90 100644
--- a/packages/ui/src/makerkit/app-breadcrumbs.tsx
+++ b/packages/ui/src/makerkit/app-breadcrumbs.tsx
@@ -1,6 +1,7 @@
'use client';
import { Fragment } from 'react';
+import clsx from 'clsx';
import { usePathname } from 'next/navigation';
@@ -52,9 +53,13 @@ export function AppBreadcrumbs(props: {
/>
);
+ const isLast = index === visiblePaths.length - 1;
+
return (
-
+
{label}
@@ -77,7 +83,7 @@ export function AppBreadcrumbs(props: {
>
)}
-
+
diff --git a/packages/ui/src/makerkit/page.tsx b/packages/ui/src/makerkit/page.tsx
index 7a4ee42..aaf48ad 100644
--- a/packages/ui/src/makerkit/page.tsx
+++ b/packages/ui/src/makerkit/page.tsx
@@ -42,7 +42,7 @@ function PageWithSidebar(props: PageProps) {
>
{MobileNavigation}
-
@@ -106,7 +106,7 @@ export function PageBody(
}>,
) {
const className = cn(
- 'flex w-full flex-1 flex-col space-y-6 lg:px-4',
+ 'flex w-full flex-1 flex-col space-y-6',
props.className,
);
@@ -119,8 +119,8 @@ export function PageNavigation(props: React.PropsWithChildren) {
export function PageDescription(props: React.PropsWithChildren) {
return (
-
-
+
@@ -158,7 +158,7 @@ export function PageHeader({
return (
@@ -168,7 +168,7 @@ export function PageHeader({
-
+
{displaySidebarTrigger ? (
) : null}
diff --git a/packages/ui/src/shadcn/card.tsx b/packages/ui/src/shadcn/card.tsx
index fec5a6b..9841c3e 100644
--- a/packages/ui/src/shadcn/card.tsx
+++ b/packages/ui/src/shadcn/card.tsx
@@ -34,7 +34,7 @@ const CardHeader: React.FC
> = ({
className,
...props
}) => (
-
+
);
CardHeader.displayName = 'CardHeader';
@@ -60,14 +60,14 @@ CardDescription.displayName = 'CardDescription';
const CardContent: React.FC> = ({
className,
...props
-}) => ;
+}) => ;
CardContent.displayName = 'CardContent';
const CardFooter: React.FC> = ({
className,
...props
}) => (
-
+
);
CardFooter.displayName = 'CardFooter';
diff --git a/public/locales/en/account.json b/public/locales/en/account.json
index 8e7020c..2872ede 100644
--- a/public/locales/en/account.json
+++ b/public/locales/en/account.json
@@ -130,7 +130,10 @@
"description": "Please enter your personal details to continue",
"button": "Continue",
"userConsentLabel": "I agree to the use of personal data on the platform",
- "userConsentUrlTitle": "View privacy policy"
+ "userConsentUrlTitle": "View privacy policy",
+ "updateAccountLoading": "Updating account details...",
+ "updateAccountSuccess": "Account details updated",
+ "updateAccountError": "Updating account details error"
},
"consentModal": {
"title": "Before we start",
diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json
index 7db0925..5c89064 100644
--- a/public/locales/en/auth.json
+++ b/public/locales/en/auth.json
@@ -22,6 +22,7 @@
"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}}",
+ "signInWithKeycloak": "Smart-ID/Mobile-ID/ID-card",
"signInWithPhoneNumber": "Sign in with Phone Number",
"signInWithEmail": "Sign in with Email",
"signUpWithEmail": "Sign up with Email",
diff --git a/public/locales/en/cart.json b/public/locales/en/cart.json
index 0c9af9a..223d3f1 100644
--- a/public/locales/en/cart.json
+++ b/public/locales/en/cart.json
@@ -3,9 +3,6 @@
"description": "View your cart",
"emptyCartMessage": "Your cart is empty",
"emptyCartMessageDescription": "Add items to your cart to continue.",
- "subtotal": "Subtotal",
- "total": "Total",
- "promotionsTotal": "Promotions total",
"table": {
"item": "Item",
"quantity": "Quantity",
@@ -25,13 +22,19 @@
"timeoutAction": "Continue"
},
"discountCode": {
- "title": "Gift card or promotion code",
- "label": "Add Promotion Code(s)",
+ "title": "Gift card or promo code",
+ "label": "Add Promo Code(s)",
"apply": "Apply",
- "subtitle": "If you wish, you can add a promotion code",
- "placeholder": "Enter promotion code",
- "remove": "Remove promotion code",
- "appliedCodes": "Promotion(s) applied:"
+ "subtitle": "If you wish, you can add a promo code",
+ "placeholder": "Enter promo code",
+ "remove": "Remove promo code",
+ "appliedCodes": "Promotions(s) applied:",
+ "removeError": "Failed to remove promo code",
+ "removeSuccess": "Promo code removed",
+ "removeLoading": "Removing promo code...",
+ "addError": "Failed to add promo code",
+ "addSuccess": "Promo code added",
+ "addLoading": "Setting promo code..."
},
"items": {
"synlabAnalyses": {
@@ -52,7 +55,11 @@
}
},
"order": {
- "title": "Order"
+ "title": "Order",
+ "promotionsTotal": "Promotions total",
+ "subtotal": "Subtotal",
+ "total": "Total",
+ "giftCard": "Gift card"
},
"orderConfirmed": {
"title": "Order confirmed",
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index b26211f..cf41acd 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -129,7 +129,9 @@
"selectDate": "Select date"
},
"formFieldError": {
- "invalidPhoneNumber": "Please enter a valid Estonian phone number (must include country code +372)"
+ "invalidPhoneNumber": "Please enter a valid Estonian phone number (must include country code +372)",
+ "invalidPersonalCode": "Please enter a valid Estonian personal code",
+ "stringNonEmpty": "This field is required"
},
"wallet": {
"balance": "Your MedReport account balance",
diff --git a/public/locales/en/order-analysis.json b/public/locales/en/order-analysis.json
index 2031316..11d1145 100644
--- a/public/locales/en/order-analysis.json
+++ b/public/locales/en/order-analysis.json
@@ -1,7 +1,6 @@
{
"title": "Select analysis",
"description": "All analysis results will appear within 1-3 days after the blood test.",
- "analysisNotAvailable": "Analysis is not available currently",
"analysisAddedToCart": "Analysis added to cart",
"analysisAddToCartError": "Adding analysis to cart failed"
}
\ No newline at end of file
diff --git a/public/locales/en/order-health-analysis.json b/public/locales/en/order-health-analysis.json
new file mode 100644
index 0000000..60633ac
--- /dev/null
+++ b/public/locales/en/order-health-analysis.json
@@ -0,0 +1,4 @@
+{
+ "title": "Order health analysis",
+ "description": "Select a suitable date and book your appointment time."
+}
\ No newline at end of file
diff --git a/public/locales/en/orders.json b/public/locales/en/orders.json
index f846b0a..7aa958c 100644
--- a/public/locales/en/orders.json
+++ b/public/locales/en/orders.json
@@ -1,6 +1,7 @@
{
"title": "Orders",
"description": "View your orders",
+ "noOrders": "No orders found",
"table": {
"analysisPackage": "Analysis package",
"otherOrders": "Order",
diff --git a/public/locales/et/account.json b/public/locales/et/account.json
index e3e824c..86b36e3 100644
--- a/public/locales/et/account.json
+++ b/public/locales/et/account.json
@@ -130,7 +130,10 @@
"description": "Jätkamiseks palun sisestage enda isikuandmed",
"button": "Jätka",
"userConsentLabel": "Nõustun isikuandmete kasutamisega platvormil",
- "userConsentUrlTitle": "Vaata isikuandmete töötlemise põhimõtteid"
+ "userConsentUrlTitle": "Vaata isikuandmete töötlemise põhimõtteid",
+ "updateAccountLoading": "Konto andmed uuendatakse...",
+ "updateAccountSuccess": "Konto andmed uuendatud",
+ "updateAccountError": "Konto andmete uuendamine ebaõnnestus"
},
"consentModal": {
"title": "Enne alustamist",
diff --git a/public/locales/et/auth.json b/public/locales/et/auth.json
index d9ebf9b..4919b25 100644
--- a/public/locales/et/auth.json
+++ b/public/locales/et/auth.json
@@ -2,7 +2,7 @@
"signUpHeading": "Loo konto",
"signUp": "Loo konto",
"signUpSubheading": "Täida allolev vorm, et luua konto.",
- "signInHeading": "Logi oma kontole sisse",
+ "signInHeading": "Logi sisse",
"signInSubheading": "Tere tulemast tagasi! Palun sisesta oma andmed",
"signIn": "Logi sisse",
"getStarted": "Alusta",
@@ -22,6 +22,7 @@
"alreadyHaveAccountStatement": "Mul on juba konto, ma tahan sisse logida",
"doNotHaveAccountStatement": "Mul pole kontot, ma tahan registreeruda",
"signInWithProvider": "Logi sisse teenusega {{provider}}",
+ "signInWithKeycloak": "Smart-ID/Mobiil-ID/ID-kaart",
"signInWithPhoneNumber": "Logi sisse telefoninumbriga",
"signInWithEmail": "Logi sisse e-posti aadressiga",
"signUpWithEmail": "Registreeru e-posti aadressiga",
@@ -68,7 +69,7 @@
"acceptTermsAndConditions": "Ma nõustun ja ",
"termsOfService": "Kasutustingimused",
"privacyPolicy": "Privaatsuspoliitika",
- "orContinueWith": "Või jätka koos",
+ "orContinueWith": "Või",
"redirecting": "Oled sees! Palun oota...",
"errors": {
"Invalid login credentials": "Sisestatud andmed on valed",
diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json
index fe87f7b..36b69d0 100644
--- a/public/locales/et/cart.json
+++ b/public/locales/et/cart.json
@@ -3,9 +3,6 @@
"description": "Vaata oma ostukorvi",
"emptyCartMessage": "Sinu ostukorv on tühi",
"emptyCartMessageDescription": "Lisa tooteid ostukorvi, et jätkata.",
- "subtotal": "Vahesumma",
- "promotionsTotal": "Soodustuse summa",
- "total": "Summa",
"table": {
"item": "Toode",
"quantity": "Kogus",
@@ -34,8 +31,10 @@
"appliedCodes": "Rakendatud sooduskoodid:",
"removeError": "Sooduskoodi eemaldamine ebaõnnestus",
"removeSuccess": "Sooduskood eemaldatud",
+ "removeLoading": "Sooduskoodi eemaldamine",
"addError": "Sooduskoodi rakendamine ebaõnnestus",
- "addSuccess": "Sooduskood rakendatud"
+ "addSuccess": "Sooduskood rakendatud",
+ "addLoading": "Rakendan sooduskoodi..."
},
"items": {
"synlabAnalyses": {
@@ -56,7 +55,11 @@
}
},
"order": {
- "title": "Tellimus"
+ "title": "Tellimus",
+ "promotionsTotal": "Soodustuse summa",
+ "subtotal": "Vahesumma",
+ "total": "Summa",
+ "giftCard": "Kinkekaart"
},
"orderConfirmed": {
"title": "Tellimus on edukalt esitatud",
diff --git a/public/locales/et/common.json b/public/locales/et/common.json
index 792cc3a..485c009 100644
--- a/public/locales/et/common.json
+++ b/public/locales/et/common.json
@@ -129,7 +129,9 @@
"selectDate": "Vali kuupäev"
},
"formFieldError": {
- "invalidPhoneNumber": "Palun sisesta Eesti telefoninumber (peab sisaldama riigikoodi +372)"
+ "invalidPhoneNumber": "Palun sisesta Eesti telefoninumber (peab sisaldama riigikoodi +372)",
+ "invalidPersonalCode": "Palun sisesta Eesti isikukood",
+ "stringNonEmpty": "See väli on kohustuslik"
},
"wallet": {
"balance": "Sinu MedReporti konto saldo",
diff --git a/public/locales/et/order-analysis.json b/public/locales/et/order-analysis.json
index 9c7b750..8ff008d 100644
--- a/public/locales/et/order-analysis.json
+++ b/public/locales/et/order-analysis.json
@@ -1,7 +1,6 @@
{
"title": "Vali analüüs",
"description": "Kõikide analüüside tulemused ilmuvad 1–3 tööpäeva jooksul peale vere andmist.",
- "analysisNotAvailable": "Analüüsi tellimine ei ole hetkel saadaval",
"analysisAddedToCart": "Analüüs lisatud ostukorvi",
"analysisAddToCartError": "Analüüsi lisamine ostukorvi ebaõnnestus"
}
\ No newline at end of file
diff --git a/public/locales/et/order-health-analysis.json b/public/locales/et/order-health-analysis.json
new file mode 100644
index 0000000..f267e09
--- /dev/null
+++ b/public/locales/et/order-health-analysis.json
@@ -0,0 +1,4 @@
+{
+ "title": "Telli terviseuuring",
+ "description": "Vali kalendrist sobiv kuupäev ja broneeri endale vastuvõtuaeg."
+}
\ No newline at end of file
diff --git a/public/locales/et/orders.json b/public/locales/et/orders.json
index 4811a2e..822c6b1 100644
--- a/public/locales/et/orders.json
+++ b/public/locales/et/orders.json
@@ -1,6 +1,7 @@
{
"title": "Tellimused",
"description": "Vaata oma tellimusi",
+ "noOrders": "Tellimusi ei leitud",
"table": {
"analysisPackage": "Analüüsi pakett",
"otherOrders": "Tellimus",
diff --git a/public/locales/ru/account.json b/public/locales/ru/account.json
index 9bfae35..bb3785b 100644
--- a/public/locales/ru/account.json
+++ b/public/locales/ru/account.json
@@ -130,7 +130,10 @@
"description": "Пожалуйста, введите личные данные для продолжения",
"button": "Продолжить",
"userConsentLabel": "Я согласен на использование персональных данных на платформе",
- "userConsentUrlTitle": "Посмотреть политику конфиденциальности"
+ "userConsentUrlTitle": "Посмотреть политику конфиденциальности",
+ "updateAccountLoading": "Обновление данных аккаунта...",
+ "updateAccountSuccess": "Данные аккаунта обновлены",
+ "updateAccountError": "Не удалось обновить данные аккаунта"
},
"consentModal": {
"title": "Перед началом",
diff --git a/public/locales/ru/auth.json b/public/locales/ru/auth.json
index 8634403..5dc5e1d 100644
--- a/public/locales/ru/auth.json
+++ b/public/locales/ru/auth.json
@@ -22,6 +22,7 @@
"alreadyHaveAccountStatement": "У меня уже есть аккаунт, я хочу войти",
"doNotHaveAccountStatement": "У меня нет аккаунта, я хочу зарегистрироваться",
"signInWithProvider": "Войти через {{provider}}",
+ "signInWithKeycloak": "Smart-ID/Mobiil-ID/ID-kaart",
"signInWithPhoneNumber": "Войти по номеру телефона",
"signInWithEmail": "Войти по Email",
"signUpWithEmail": "Зарегистрироваться по Email",
diff --git a/public/locales/ru/cart.json b/public/locales/ru/cart.json
index 7f3c536..289ff31 100644
--- a/public/locales/ru/cart.json
+++ b/public/locales/ru/cart.json
@@ -3,8 +3,6 @@
"description": "Просмотрите свою корзину",
"emptyCartMessage": "Ваша корзина пуста",
"emptyCartMessageDescription": "Добавьте товары в корзину, чтобы продолжить.",
- "subtotal": "Промежуточный итог",
- "total": "Сумма",
"table": {
"item": "Товар",
"quantity": "Количество",
@@ -33,8 +31,10 @@
"appliedCodes": "Примененные промокоды:",
"removeError": "Не удалось удалить промокод",
"removeSuccess": "Промокод удален",
+ "removeLoading": "Удаление промокода...",
"addError": "Не удалось применить промокод",
- "addSuccess": "Промокод применен"
+ "addSuccess": "Промокод применен",
+ "addLoading": "Применение промокода..."
},
"items": {
"synlabAnalyses": {
@@ -55,7 +55,11 @@
}
},
"order": {
- "title": "Заказ"
+ "title": "Заказ",
+ "promotionsTotal": "Скидка",
+ "subtotal": "Промежуточный итог",
+ "total": "Сумма",
+ "giftCard": "Подарочная карта"
},
"orderConfirmed": {
"title": "Заказ успешно оформлен",
diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json
index 545b9e6..28b5d6b 100644
--- a/public/locales/ru/common.json
+++ b/public/locales/ru/common.json
@@ -128,6 +128,11 @@
"amount": "Сумма",
"selectDate": "Выберите дату"
},
+ "formFieldError": {
+ "invalidPhoneNumber": "Пожалуйста, введите действительный номер телефона (должен включать код страны +372)",
+ "invalidPersonalCode": "Пожалуйста, введите действительный персональный код",
+ "stringNonEmpty": "Это поле обязательно"
+ },
"wallet": {
"balance": "Баланс вашего счета MedReport",
"expiredAt": "Действительно до {{expiredAt}}"
diff --git a/public/locales/ru/order-analysis.json b/public/locales/ru/order-analysis.json
index ea36b5c..c837255 100644
--- a/public/locales/ru/order-analysis.json
+++ b/public/locales/ru/order-analysis.json
@@ -1,7 +1,6 @@
{
"title": "Выберите анализ",
"description": "Результаты всех анализов будут доступны в течение 1–3 рабочих дней после сдачи крови.",
- "analysisNotAvailable": "Заказ анализа в данный момент недоступен",
"analysisAddedToCart": "Анализ добавлен в корзину",
"analysisAddToCartError": "Не удалось добавить анализ в корзину"
}
\ No newline at end of file
diff --git a/public/locales/ru/order-health-analysis.json b/public/locales/ru/order-health-analysis.json
new file mode 100644
index 0000000..75c65f7
--- /dev/null
+++ b/public/locales/ru/order-health-analysis.json
@@ -0,0 +1,4 @@
+{
+ "title": "Заказать анализ здоровья",
+ "description": "Выберите подходящую дату и забронируйте время для вашего приёма."
+}
\ No newline at end of file
diff --git a/public/locales/ru/orders.json b/public/locales/ru/orders.json
index c42a230..6669aff 100644
--- a/public/locales/ru/orders.json
+++ b/public/locales/ru/orders.json
@@ -1,6 +1,7 @@
{
"title": "Заказы",
"description": "Просмотрите ваши заказы",
+ "noOrders": "Заказы не найдены",
"table": {
"analysisPackage": "Пакет анализов",
"otherOrders": "Заказ",