diff --git a/sigap-website/.vscode/settings.json b/sigap-website/.vscode/settings.json index 86953b0..8bb69fd 100644 --- a/sigap-website/.vscode/settings.json +++ b/sigap-website/.vscode/settings.json @@ -1,3 +1,3 @@ { - "files.autoSave": "afterDelay" + "files.autoSave": "off" } diff --git a/sigap-website/app/(protected)/(admin)/layout.tsx b/sigap-website/app/(protected)/(admin)/layout.tsx index f20db60..e6f1374 100644 --- a/sigap-website/app/(protected)/(admin)/layout.tsx +++ b/sigap-website/app/(protected)/(admin)/layout.tsx @@ -69,7 +69,7 @@ export default async function Layout({
- + + + e.preventDefault()} + > + {searchable && ( +
+
+ + { + if (e.key === "Escape") { + e.stopPropagation(); + setIsOpen(false); + } + }} + disabled={disabled} + /> +
+ )} + + {currentOptions.length > 0 && ( + <> +
+

+ {currentLabel} +

+ {currentOptions.map((option) => ( + +
+ {option.label} + {option.subLabel && ( + + {option.subLabel} + + )} +
+ +
+ ))} +
+ + + )} + +
+

{selectLabel}

+ {selectableOptions.length === 0 ? ( +

+ No timezones found +

+ ) : ( + selectableOptions.map((option) => ( + handleOptionSelect(option.value)} + disabled={disabled} + > +
+ {option.label} + {option.subLabel && ( + + {option.subLabel} + + )} +
+ {option.beta && ( + + BETA + + )} +
+ )) + )} +
+
+
+ ); +}; + +export default DropdownSwitcher; diff --git a/sigap-website/app/_components/theme-switcher.tsx b/sigap-website/app/_components/theme-switcher.tsx index f503191..dcaf39a 100644 --- a/sigap-website/app/_components/theme-switcher.tsx +++ b/sigap-website/app/_components/theme-switcher.tsx @@ -14,8 +14,20 @@ import { Laptop, Moon, Sun, type LucideIcon } from "lucide-react"; import { useTheme } from "next-themes"; import { useEffect, useState, useMemo } from "react"; +type Variant = + | "link" + | "outline" + | "default" + | "destructive" + | "secondary" + | "ghost"; + interface ThemeSwitcherComponentProps { showTitle?: boolean; + title?: string; + prefix?: boolean; + suffix?: LucideIcon; + variant?: Variant; } type ThemeOption = { @@ -34,6 +46,10 @@ const themeOptions: ThemeOption[] = [ const ThemeSwitcherComponent = ({ showTitle = false, + title, + prefix = true, + suffix, + variant = "outline", }: ThemeSwitcherComponentProps) => { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); @@ -56,18 +72,28 @@ const ThemeSwitcherComponent = ({ diff --git a/sigap-website/prisma/data/languages.ts b/sigap-website/prisma/data/languages.ts new file mode 100644 index 0000000..2b9ef35 --- /dev/null +++ b/sigap-website/prisma/data/languages.ts @@ -0,0 +1,54 @@ +import { ChevronDown, LucideIcon } from "lucide-react"; + +export type LanguageType = { + value: string; + prefix?: LucideIcon; + label: string; + subLabel: string; + beta?: boolean; +}; + +export const languages = [ + { + value: "en-US", + prefix: ChevronDown, + label: "English", + subLabel: "English (US)", + beta: true, + }, + { + value: "id-ID", + prefix: ChevronDown, + label: "Indonesian", + subLabel: "Indonesia", + beta: true, + }, + { + value: "ja-JP", + prefix: ChevronDown, + label: "日本語", + subLabel: "Japanese", + beta: true, + }, + { + value: "ko-KR", + prefix: ChevronDown, + label: "한국어", + subLabel: "Korean", + beta: true, + }, + { + value: "zh-CN", + prefix: ChevronDown, + label: "中文", + subLabel: "Chinese (Simplified)", + beta: true, + }, + { + value: "es-419", + prefix: ChevronDown, + label: "Español (Latinoamérica)", + subLabel: "Spanish (Latin America)", + beta: true, + }, +]; diff --git a/sigap-website/prisma/data/timezones.ts b/sigap-website/prisma/data/timezones.ts new file mode 100644 index 0000000..77a28f6 --- /dev/null +++ b/sigap-website/prisma/data/timezones.ts @@ -0,0 +1,42 @@ +import { ChevronDown, LucideIcon } from "lucide-react"; + +export type TimezoneType = { + value: string; + prefix?: LucideIcon; + label: string; + subLabel: string; +}; + +// Initial timezone options +export const initialTimezones = [ + { + value: "Asia/Jakarta", + prefix: ChevronDown, + label: "Jakarta", + subLabel: "(GMT+7:00)", + }, + { + value: "Asia/Singapore", + prefix: ChevronDown, + label: "Singapore", + subLabel: "(GMT+8:00)", + }, + { + value: "Asia/Tokyo", + prefix: ChevronDown, + label: "Tokyo", + subLabel: "(GMT+9:00)", + }, + { + value: "Australia/Adelaide", + prefix: ChevronDown, + label: "Adelaide", + subLabel: "(GMT+9:30)", + }, + { + value: "Australia/Sydney", + prefix: ChevronDown, + label: "Sydney", + subLabel: "(GMT+10:00)", + }, +]; diff --git a/sigap-website/utils/cookies-manager.ts b/sigap-website/utils/cookies-manager.ts new file mode 100644 index 0000000..e71fb64 --- /dev/null +++ b/sigap-website/utils/cookies-manager.ts @@ -0,0 +1,162 @@ +// Cookie management utility functions + +// Cookie types +export type CookiePreferences = { + necessary: boolean; + functional: boolean; + analytics: boolean; + marketing?: boolean; +}; + +// Default cookie preferences +export const defaultCookiePreferences: CookiePreferences = { + necessary: true, // Always true, cannot be disabled + functional: false, + analytics: false, + marketing: false, +}; + +// Cookie names +const COOKIE_PREFERENCES_KEY = "cookie-preferences"; +const LANGUAGE_PREFERENCE_KEY = "language-preference"; +const TIMEZONE_PREFERENCE_KEY = "timezone-preference"; +const WEEK_START_PREFERENCE_KEY = "week-start-preference"; +const AUTO_TIMEZONE_PREFERENCE_KEY = "auto-timezone-preference"; + +// Helper to set a cookie with expiration +export const setCookie = ( + name: string, + value: string, + days: number = 365 +): void => { + const date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + const expires = `; expires=${date.toUTCString()}`; + document.cookie = `${name}=${value}${expires}; path=/; SameSite=Lax`; +}; + +// Helper to get a cookie by name +export const getCookie = (name: string): string | null => { + if (typeof document === "undefined") return null; + + const nameEQ = `${name}=`; + const ca = document.cookie.split(";"); + + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === " ") c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); + } + + return null; +}; + +// Helper to delete a cookie +export const deleteCookie = (name: string): void => { + setCookie(name, "", -1); +}; + +// Save cookie preferences +export const saveCookiePreferences = (preferences: CookiePreferences): void => { + // Always ensure necessary cookies are enabled + const prefsToSave = { + ...preferences, + necessary: true, + }; + + setCookie(COOKIE_PREFERENCES_KEY, JSON.stringify(prefsToSave)); +}; + +// Get cookie preferences +export const getCookiePreferences = (): CookiePreferences => { + try { + const preferences = getCookie(COOKIE_PREFERENCES_KEY); + if (preferences) { + return { ...defaultCookiePreferences, ...JSON.parse(preferences) }; + } + } catch (error) { + console.error("Error parsing cookie preferences:", error); + } + + return defaultCookiePreferences; +}; + +// Save language preference +export const saveLanguagePreference = (language: string): void => { + setCookie(LANGUAGE_PREFERENCE_KEY, language); +}; + +// Get language preference +export const getLanguagePreference = (): string | null => { + return getCookie(LANGUAGE_PREFERENCE_KEY); +}; + +// Save timezone preference +export const saveTimezonePreference = (timezone: string): void => { + setCookie(TIMEZONE_PREFERENCE_KEY, timezone); +}; + +// Get timezone preference +export const getTimezonePreference = (): string | null => { + return getCookie(TIMEZONE_PREFERENCE_KEY); +}; + +// Save week start preference +export const saveWeekStartPreference = (startOnMonday: boolean): void => { + setCookie(WEEK_START_PREFERENCE_KEY, startOnMonday ? "monday" : "sunday"); +}; + +// Get week start preference +export const getWeekStartPreference = (): boolean => { + const pref = getCookie(WEEK_START_PREFERENCE_KEY); + return pref === "monday"; +}; + +// Save auto timezone preference +export const saveAutoTimezonePreference = (auto: boolean): void => { + setCookie(AUTO_TIMEZONE_PREFERENCE_KEY, auto ? "true" : "false"); +}; + +// Get auto timezone preference +export const getAutoTimezonePreference = (): boolean => { + const pref = getCookie(AUTO_TIMEZONE_PREFERENCE_KEY); + return pref === "true"; +}; + +// Apply cookie preferences to the application +export const applyCookiePreferences = ( + preferences: CookiePreferences +): void => { + // This function would implement the actual cookie policy enforcement + // For example, enabling/disabling analytics scripts based on preferences + + if (preferences.analytics) { + // Enable analytics scripts + console.log("Analytics cookies enabled"); + // Example: initAnalytics(); + } else { + // Disable analytics scripts + console.log("Analytics cookies disabled"); + // Example: disableAnalytics(); + } + + if (preferences.functional) { + // Enable functional cookies + console.log("Functional cookies enabled"); + // Example: enableFunctionalFeatures(); + } else { + // Disable functional cookies + console.log("Functional cookies disabled"); + // Example: disableFunctionalFeatures(); + } + + if (preferences.marketing) { + // Enable marketing cookies + console.log("Marketing cookies enabled"); + // Example: enableMarketingFeatures(); + } else { + // Disable marketing cookies + console.log("Marketing cookies disabled"); + // Example: disableMarketingFeatures(); + } +}; diff --git a/sigap-website/utils/notification-cookies-manager.ts b/sigap-website/utils/notification-cookies-manager.ts new file mode 100644 index 0000000..71ec4da --- /dev/null +++ b/sigap-website/utils/notification-cookies-manager.ts @@ -0,0 +1,101 @@ +// Notification preferences cookie management + +// Notification preferences type +export type NotificationPreferences = { + mobilePushNotifications: boolean; + emailNotifications: boolean; + announcementNotifications: boolean; +}; + +// Default notification preferences +export const defaultNotificationPreferences: NotificationPreferences = { + mobilePushNotifications: true, + emailNotifications: true, + announcementNotifications: false, +}; + +// Cookie names +const NOTIFICATION_PREFERENCES_KEY = "notification-preferences"; + +// Helper to set a cookie with expiration +export const setCookie = (name: string, value: string, days = 365): void => { + const date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + const expires = `; expires=${date.toUTCString()}`; + document.cookie = `${name}=${value}${expires}; path=/; SameSite=Lax`; +}; + +// Helper to get a cookie by name +export const getCookie = (name: string): string | null => { + if (typeof document === "undefined") return null; + + const nameEQ = `${name}=`; + const ca = document.cookie.split(";"); + + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === " ") c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); + } + + return null; +}; + +// Save notification preferences +export const saveNotificationPreferences = ( + preferences: NotificationPreferences +): void => { + setCookie(NOTIFICATION_PREFERENCES_KEY, JSON.stringify(preferences)); +}; + +// Get notification preferences +export const getNotificationPreferences = (): NotificationPreferences => { + try { + const preferences = getCookie(NOTIFICATION_PREFERENCES_KEY); + if (preferences) { + return { ...defaultNotificationPreferences, ...JSON.parse(preferences) }; + } + } catch (error) { + console.error("Error parsing notification preferences:", error); + } + + return defaultNotificationPreferences; +}; + +// Apply notification preferences to the application +export const applyNotificationPreferences = ( + preferences: NotificationPreferences +): void => { + // This function would implement the actual notification settings + // For example, enabling/disabling push notifications + + if (preferences.mobilePushNotifications) { + // Enable push notifications + console.log("Push notifications enabled"); + // Example: requestPushPermission(); + } else { + // Disable push notifications + console.log("Push notifications disabled"); + // Example: disablePushNotifications(); + } + + if (preferences.emailNotifications) { + // Enable email notifications + console.log("Email notifications enabled"); + // Example: enableEmailNotifications(); + } else { + // Disable email notifications + console.log("Email notifications disabled"); + // Example: disableEmailNotifications(); + } + + if (preferences.announcementNotifications) { + // Enable announcement notifications + console.log("Announcement notifications enabled"); + // Example: subscribeToAnnouncementEmails(); + } else { + // Disable announcement notifications + console.log("Announcement notifications disabled"); + // Example: unsubscribeFromAnnouncementEmails(); + } +};