Installed Laravel
This commit is contained in:
92
resources/js/composables/useAppearance.ts
Normal file
92
resources/js/composables/useAppearance.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
type Appearance = 'light' | 'dark' | 'system';
|
||||
|
||||
export function updateTheme(value: Appearance) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'system') {
|
||||
const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const systemTheme = mediaQueryList.matches ? 'dark' : 'light';
|
||||
|
||||
document.documentElement.classList.toggle('dark', systemTheme === 'dark');
|
||||
} else {
|
||||
document.documentElement.classList.toggle('dark', value === 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
const setCookie = (name: string, value: string, days = 365) => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxAge = days * 24 * 60 * 60;
|
||||
|
||||
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
|
||||
};
|
||||
|
||||
const mediaQuery = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)');
|
||||
};
|
||||
|
||||
const getStoredAppearance = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return localStorage.getItem('appearance') as Appearance | null;
|
||||
};
|
||||
|
||||
const handleSystemThemeChange = () => {
|
||||
const currentAppearance = getStoredAppearance();
|
||||
|
||||
updateTheme(currentAppearance || 'system');
|
||||
};
|
||||
|
||||
export function initializeTheme() {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize theme from saved preference or default to system...
|
||||
const savedAppearance = getStoredAppearance();
|
||||
updateTheme(savedAppearance || 'system');
|
||||
|
||||
// Set up system theme change listener...
|
||||
mediaQuery()?.addEventListener('change', handleSystemThemeChange);
|
||||
}
|
||||
|
||||
const appearance = ref<Appearance>('system');
|
||||
|
||||
export function useAppearance() {
|
||||
onMounted(() => {
|
||||
const savedAppearance = localStorage.getItem('appearance') as Appearance | null;
|
||||
|
||||
if (savedAppearance) {
|
||||
appearance.value = savedAppearance;
|
||||
}
|
||||
});
|
||||
|
||||
function updateAppearance(value: Appearance) {
|
||||
appearance.value = value;
|
||||
|
||||
// Store in localStorage for client-side persistence...
|
||||
localStorage.setItem('appearance', value);
|
||||
|
||||
// Store in cookie for SSR...
|
||||
setCookie('appearance', value);
|
||||
|
||||
updateTheme(value);
|
||||
}
|
||||
|
||||
return {
|
||||
appearance,
|
||||
updateAppearance,
|
||||
};
|
||||
}
|
||||
14
resources/js/composables/useInitials.ts
Normal file
14
resources/js/composables/useInitials.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function getInitials(fullName?: string): string {
|
||||
if (!fullName) return '';
|
||||
|
||||
const names = fullName.trim().split(' ');
|
||||
|
||||
if (names.length === 0) return '';
|
||||
if (names.length === 1) return names[0].charAt(0).toUpperCase();
|
||||
|
||||
return `${names[0].charAt(0)}${names[names.length - 1].charAt(0)}`.toUpperCase();
|
||||
}
|
||||
|
||||
export function useInitials() {
|
||||
return { getInitials };
|
||||
}
|
||||
79
resources/js/composables/useTwoFactorAuth.ts
Normal file
79
resources/js/composables/useTwoFactorAuth.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { qrCode, recoveryCodes, secretKey } from '@/routes/two-factor';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const fetchJson = async <T>(url: string): Promise<T> => {
|
||||
const response = await fetch(url, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const qrCodeSvg = ref<string | null>(null);
|
||||
const manualSetupKey = ref<string | null>(null);
|
||||
const recoveryCodesList = ref<string[]>([]);
|
||||
|
||||
const hasSetupData = computed<boolean>(() => qrCodeSvg.value !== null && manualSetupKey.value !== null);
|
||||
|
||||
export const useTwoFactorAuth = () => {
|
||||
const fetchQrCode = async (): Promise<void> => {
|
||||
const { svg } = await fetchJson<{ svg: string; url: string }>(qrCode.url());
|
||||
|
||||
qrCodeSvg.value = svg;
|
||||
};
|
||||
|
||||
const fetchSetupKey = async (): Promise<void> => {
|
||||
const { secretKey: key } = await fetchJson<{ secretKey: string }>(secretKey.url());
|
||||
|
||||
manualSetupKey.value = key;
|
||||
};
|
||||
|
||||
const clearSetupData = (): void => {
|
||||
manualSetupKey.value = null;
|
||||
qrCodeSvg.value = null;
|
||||
};
|
||||
|
||||
const clearTwoFactorAuthData = (): void => {
|
||||
clearSetupData();
|
||||
|
||||
recoveryCodesList.value = [];
|
||||
};
|
||||
|
||||
const fetchRecoveryCodes = async (): Promise<void> => {
|
||||
try {
|
||||
recoveryCodesList.value = await fetchJson<string[]>(recoveryCodes.url());
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch recovery codes:', error);
|
||||
|
||||
recoveryCodesList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSetupData = async (): Promise<void> => {
|
||||
try {
|
||||
await Promise.all([fetchQrCode(), fetchSetupKey()]);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch setup data:', error);
|
||||
|
||||
qrCodeSvg.value = null;
|
||||
manualSetupKey.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
qrCodeSvg,
|
||||
manualSetupKey,
|
||||
recoveryCodesList,
|
||||
hasSetupData,
|
||||
clearSetupData,
|
||||
clearTwoFactorAuthData,
|
||||
fetchQrCode,
|
||||
fetchSetupKey,
|
||||
fetchSetupData,
|
||||
fetchRecoveryCodes,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user