account, schduels, timezone profile and many imporant updates
This commit is contained in:
@@ -22,6 +22,8 @@ import PageHeader from '../../components/common/PageHeader';
|
||||
import ComponentCard from '../../components/common/ComponentCard';
|
||||
import DebugSiteSelector from '../../components/common/DebugSiteSelector';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import { formatDate, formatDateTime } from '../../utils/date';
|
||||
import { getAccountTimezone } from '../../utils/timezone';
|
||||
import {
|
||||
BoltIcon,
|
||||
ListIcon,
|
||||
@@ -81,7 +83,7 @@ const AutomationPage: React.FC = () => {
|
||||
|
||||
// Server time state - shows the actual time used for all operations
|
||||
const [serverTime, setServerTime] = useState<string | null>(null);
|
||||
const [serverTimezone, setServerTimezone] = useState<string>('UTC');
|
||||
const accountTimezone = getAccountTimezone();
|
||||
|
||||
// Track site ID to avoid duplicate calls when activeSite object reference changes
|
||||
const siteId = activeSite?.id;
|
||||
@@ -91,8 +93,7 @@ const AutomationPage: React.FC = () => {
|
||||
const loadServerTime = async () => {
|
||||
try {
|
||||
const data = await automationService.getServerTime();
|
||||
setServerTime(data.server_time_formatted);
|
||||
setServerTimezone(data.timezone);
|
||||
setServerTime(data.server_time);
|
||||
} catch (error) {
|
||||
console.error('Failed to load server time:', error);
|
||||
}
|
||||
@@ -111,12 +112,14 @@ const AutomationPage: React.FC = () => {
|
||||
const getNextRunTime = (config: AutomationConfig): string => {
|
||||
if (!config.is_enabled || !config.scheduled_time) return '';
|
||||
|
||||
const now = new Date();
|
||||
const now = serverTime ? new Date(serverTime) : new Date();
|
||||
const [schedHours, schedMinutes] = config.scheduled_time.split(':').map(Number);
|
||||
|
||||
// Create next run date
|
||||
const nextRun = new Date();
|
||||
nextRun.setUTCHours(schedHours, schedMinutes, 0, 0);
|
||||
// Prefer server-provided next run time if available
|
||||
const nextRun = config.next_run_at ? new Date(config.next_run_at) : new Date();
|
||||
if (!config.next_run_at) {
|
||||
nextRun.setUTCHours(schedHours, schedMinutes, 0, 0);
|
||||
}
|
||||
|
||||
// If scheduled time has passed today, set to tomorrow
|
||||
if (nextRun <= now) {
|
||||
@@ -145,6 +148,17 @@ const AutomationPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (value: string | null) => {
|
||||
if (!value) return '--:--';
|
||||
const date = new Date(value);
|
||||
if (isNaN(date.getTime())) return '--:--';
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: accountTimezone,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!siteId) return;
|
||||
// Reset state when site changes
|
||||
@@ -600,7 +614,7 @@ const AutomationPage: React.FC = () => {
|
||||
</div>
|
||||
<div className="h-4 w-px bg-white/25"></div>
|
||||
<div className="text-sm text-white/80">
|
||||
Last: <span className="font-medium">{config.last_run_at ? new Date(config.last_run_at).toLocaleDateString() : 'Never'}</span>
|
||||
Last: <span className="font-medium">{config.last_run_at ? formatDate(config.last_run_at) : 'Never'}</span>
|
||||
</div>
|
||||
{config.is_enabled && (
|
||||
<>
|
||||
@@ -613,7 +627,7 @@ const AutomationPage: React.FC = () => {
|
||||
<div className="h-4 w-px bg-white/25"></div>
|
||||
<div className="text-sm text-white inline-flex items-center gap-1">
|
||||
<TimeIcon className="size-3.5" />
|
||||
<span className="font-semibold tabular-nums">{serverTime ? serverTime.substring(0, 5) : '--:--'}</span>
|
||||
<span className="font-semibold tabular-nums">{formatTime(serverTime)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -632,7 +646,7 @@ const AutomationPage: React.FC = () => {
|
||||
{!currentRun && totalPending === 0 && 'No Items Pending'}
|
||||
</span>
|
||||
<span className={`text-xs ${totalPending > 0 || currentRun ? 'text-gray-600' : 'text-white/70'}`}>
|
||||
{currentRun ? `Started: ${new Date(currentRun.started_at).toLocaleTimeString()}` : (totalPending > 0 ? `${totalPending} items in pipeline` : 'All stages clear')}
|
||||
{currentRun ? `Started: ${formatDateTime(currentRun.started_at)}` : (totalPending > 0 ? `${totalPending} items in pipeline` : 'All stages clear')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function ContentCalendar() {
|
||||
|
||||
// Schedule modal state
|
||||
const [showScheduleModal, setShowScheduleModal] = useState(false);
|
||||
const [scheduleContent, setScheduleContent] = useState<Content | null>(null);
|
||||
const [scheduledContentItem, setScheduledContentItem] = useState<Content | null>(null);
|
||||
const [isRescheduling, setIsRescheduling] = useState(false);
|
||||
|
||||
// Derived state: Queue items (scheduled or publishing - exclude already published)
|
||||
@@ -87,11 +87,6 @@ export default function ContentCalendar() {
|
||||
return dateA - dateB;
|
||||
});
|
||||
|
||||
console.log('[ContentCalendar] queueItems (derived):', items.length, 'items');
|
||||
items.forEach(item => {
|
||||
console.log(' Queue item:', item.id, item.title, 'scheduled:', item.scheduled_publish_at);
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [allContent]);
|
||||
|
||||
@@ -122,11 +117,6 @@ export default function ContentCalendar() {
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
console.log('[ContentCalendar] failedItems (derived):', items.length, 'items');
|
||||
items.forEach(item => {
|
||||
console.log(' Failed item:', item.id, item.title, 'error:', item.site_status_message);
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [allContent]);
|
||||
|
||||
@@ -136,14 +126,9 @@ export default function ContentCalendar() {
|
||||
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// DEBUG: Check scheduled items in stats calculation
|
||||
const scheduledItems = allContent.filter((c: Content) =>
|
||||
c.site_status === 'scheduled' && (!c.external_id || c.external_id === '')
|
||||
);
|
||||
console.log('[ContentCalendar] STATS CALCULATION - Scheduled items:', scheduledItems.length);
|
||||
scheduledItems.forEach(c => {
|
||||
console.log(' Stats scheduled item:', c.id, c.title, 'external_id:', c.external_id);
|
||||
});
|
||||
|
||||
// Published in last 30 days - check EITHER external_id OR site_status='published'
|
||||
const publishedLast30Days = allContent.filter((c: Content) => {
|
||||
@@ -182,10 +167,7 @@ export default function ContentCalendar() {
|
||||
}, [allContent]);
|
||||
|
||||
const loadQueue = useCallback(async () => {
|
||||
if (!activeSite?.id) {
|
||||
console.log('[ContentCalendar] No active site selected, skipping load');
|
||||
return;
|
||||
}
|
||||
if (!activeSite?.id) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -196,11 +178,6 @@ export default function ContentCalendar() {
|
||||
|
||||
const siteId = activeSite.id;
|
||||
|
||||
console.log('[ContentCalendar] ========== SITE FILTERING DEBUG ==========');
|
||||
console.log('[ContentCalendar] Active site ID:', siteId);
|
||||
console.log('[ContentCalendar] Active site name:', activeSite.name);
|
||||
console.log('[ContentCalendar] Fetching content with multiple targeted queries...');
|
||||
|
||||
// Fetch scheduled items (all of them, regardless of page)
|
||||
const scheduledResponse = await fetchAPI(
|
||||
`/v1/writer/content/?site_id=${siteId}&page_size=1000&site_status=scheduled`
|
||||
@@ -240,34 +217,6 @@ export default function ContentCalendar() {
|
||||
new Map(allItems.map(item => [item.id, item])).values()
|
||||
);
|
||||
|
||||
// Debug: Comprehensive logging
|
||||
console.log('[ContentCalendar] ========== DATA LOAD DEBUG ==========');
|
||||
console.log('[ContentCalendar] Scheduled query returned:', scheduledResponse.results?.length, 'items');
|
||||
console.log('[ContentCalendar] Failed query returned:', failedResponse.results?.length, 'items');
|
||||
console.log('[ContentCalendar] Review query returned:', reviewResponse.results?.length, 'items');
|
||||
console.log('[ContentCalendar] Approved query returned:', approvedResponse.results?.length, 'items');
|
||||
console.log('[ContentCalendar] Published query returned:', publishedResponse.results?.length, 'items');
|
||||
console.log('[ContentCalendar] Total unique items after deduplication:', uniqueItems.length);
|
||||
|
||||
console.log('[ContentCalendar] ALL SCHEDULED ITEMS DETAILS:');
|
||||
scheduledResponse.results?.forEach(c => {
|
||||
console.log(' - ID:', c.id, '| Title:', c.title);
|
||||
console.log(' status:', c.status, '| site_status:', c.site_status);
|
||||
console.log(' scheduled_publish_at:', c.scheduled_publish_at);
|
||||
console.log(' external_id:', c.external_id);
|
||||
console.log(' ---');
|
||||
});
|
||||
|
||||
console.log('[ContentCalendar] ALL FAILED ITEMS DETAILS:');
|
||||
failedResponse.results?.forEach(c => {
|
||||
console.log(' - ID:', c.id, '| Title:', c.title);
|
||||
console.log(' status:', c.status, '| site_status:', c.site_status);
|
||||
console.log(' site_status_message:', c.site_status_message);
|
||||
console.log(' scheduled_publish_at:', c.scheduled_publish_at);
|
||||
console.log(' ---');
|
||||
});
|
||||
console.log('[ContentCalendar] ====================================');
|
||||
|
||||
setAllContent(uniqueItems);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load content: ${error.message}`);
|
||||
@@ -279,11 +228,8 @@ export default function ContentCalendar() {
|
||||
// Load queue when active site changes
|
||||
useEffect(() => {
|
||||
if (activeSite?.id) {
|
||||
console.log('[ContentCalendar] Site changed to:', activeSite.id, activeSite.name);
|
||||
console.log('[ContentCalendar] Triggering loadQueue...');
|
||||
loadQueue();
|
||||
} else {
|
||||
console.log('[ContentCalendar] No active site, clearing content');
|
||||
setAllContent([]);
|
||||
}
|
||||
}, [activeSite?.id]); // Only depend on activeSite.id, loadQueue is stable
|
||||
@@ -306,7 +252,7 @@ export default function ContentCalendar() {
|
||||
|
||||
// Open reschedule modal
|
||||
const openRescheduleModal = useCallback((item: Content) => {
|
||||
setScheduleContent(item);
|
||||
setScheduledContentItem(item);
|
||||
setIsRescheduling(true);
|
||||
setShowScheduleModal(true);
|
||||
}, []);
|
||||
@@ -321,7 +267,7 @@ export default function ContentCalendar() {
|
||||
loadQueue();
|
||||
}
|
||||
setShowScheduleModal(false);
|
||||
setScheduleContent(null);
|
||||
setScheduledContentItem(null);
|
||||
setIsRescheduling(false);
|
||||
}, [isRescheduling, handleRescheduleContent, toast, loadQueue]);
|
||||
|
||||
@@ -687,10 +633,14 @@ export default function ContentCalendar() {
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrow.setHours(9, 0, 0, 0);
|
||||
scheduleContent(draggedItem.id, tomorrow.toISOString())
|
||||
.then((updatedContent) => {
|
||||
.then((response) => {
|
||||
toast.success(`Scheduled for ${tomorrow.toLocaleDateString()}`);
|
||||
setAllContent(prevContent => [
|
||||
...prevContent.map(c => c.id === draggedItem.id ? updatedContent : c)
|
||||
...prevContent.map(c => c.id === draggedItem.id ? {
|
||||
...c,
|
||||
site_status: response.site_status,
|
||||
scheduled_publish_at: response.scheduled_publish_at,
|
||||
} : c)
|
||||
]);
|
||||
})
|
||||
.catch((err) => toast.error(`Failed to schedule: ${err.message}`));
|
||||
@@ -1008,15 +958,15 @@ export default function ContentCalendar() {
|
||||
</div>
|
||||
|
||||
{/* Schedule/Reschedule Modal */}
|
||||
{showScheduleModal && scheduleContent && (
|
||||
{showScheduleModal && scheduledContentItem && (
|
||||
<ScheduleContentModal
|
||||
isOpen={showScheduleModal}
|
||||
onClose={() => {
|
||||
setShowScheduleModal(false);
|
||||
setScheduleContent(null);
|
||||
setScheduledContentItem(null);
|
||||
setIsRescheduling(false);
|
||||
}}
|
||||
content={scheduleContent}
|
||||
content={scheduledContentItem}
|
||||
onSchedule={handleScheduleFromModal}
|
||||
mode={isRescheduling ? 'reschedule' : 'schedule'}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getAccountTimezone } from '../../utils/timezone';
|
||||
/**
|
||||
* AI & Automation Settings Component
|
||||
* Per SETTINGS-CONSOLIDATION-PLAN.md
|
||||
@@ -914,11 +915,11 @@ export default function AIAutomationSettings({ siteId }: AIAutomationSettingsPro
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Timezone</span>
|
||||
<p className="text-sm font-semibold text-gray-900 dark:text-white">UTC</p>
|
||||
<p className="text-sm font-semibold text-gray-900 dark:text-white">{getAccountTimezone()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
The scheduler runs at 5 minutes past each hour. Select the hour you want your automation to run — for example, selecting 2 PM means it will run at 2:05 PM UTC. Automations only run once per day. If it has already run today, the next run will be tomorrow (or the next scheduled day for weekly/monthly).
|
||||
The scheduler runs at 5 minutes past each hour. Select the hour you want your automation to run — for example, selecting 2 PM means it will run at 2:05 PM {getAccountTimezone()}. Automations only run once per day. If it has already run today, the next run will be tomorrow (or the next scheduled day for weekly/monthly).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -474,7 +474,7 @@ export default function Approved() {
|
||||
// Open site settings in new tab
|
||||
const handleOpenSiteSettings = useCallback(() => {
|
||||
if (activeSite) {
|
||||
window.open(`/sites/${activeSite.id}/settings?tab=publishing`, '_blank');
|
||||
window.open(`/sites/${activeSite.id}/settings?tab=automation`, '_blank');
|
||||
}
|
||||
}, [activeSite]);
|
||||
|
||||
@@ -630,13 +630,16 @@ export default function Approved() {
|
||||
if (action === 'bulk_publish_site') {
|
||||
await handleBulkPublishToSite(ids);
|
||||
} else if (action === 'bulk_schedule_manual') {
|
||||
// Manual bulk scheduling (same time for all)
|
||||
handleBulkScheduleManual(ids);
|
||||
// Manual bulk scheduling (same time for all) via modal
|
||||
const numericIds = ids.map(id => parseInt(id));
|
||||
const items = content.filter(item => numericIds.includes(item.id));
|
||||
setBulkScheduleItems(items);
|
||||
setShowBulkScheduleModal(true);
|
||||
} else if (action === 'bulk_schedule_defaults') {
|
||||
// Schedule with site defaults
|
||||
handleBulkScheduleWithDefaults(ids);
|
||||
}
|
||||
}, [handleBulkPublishToSite, handleBulkScheduleManual, handleBulkScheduleWithDefaults]);
|
||||
}, [handleBulkPublishToSite, handleBulkScheduleWithDefaults, content]);
|
||||
|
||||
// Bulk status update handler
|
||||
const handleBulkUpdateStatus = useCallback(async (ids: string[], status: string) => {
|
||||
|
||||
@@ -20,6 +20,8 @@ import { Modal } from '../../components/ui/modal';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
import { TIMEZONE_OPTIONS, getTimezoneForCountry } from '../../constants/timezones';
|
||||
import { fetchCountries } from '../../utils/countries';
|
||||
import {
|
||||
getAccountSettings,
|
||||
updateAccountSettings,
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
inviteTeamMember,
|
||||
removeTeamMember,
|
||||
getUserProfile,
|
||||
updateUserProfile,
|
||||
changePassword,
|
||||
type AccountSettings,
|
||||
type TeamMember,
|
||||
@@ -40,6 +43,8 @@ export default function AccountSettingsPage() {
|
||||
const [savingProfile, setSavingProfile] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [success, setSuccess] = useState<string>('');
|
||||
const [countries, setCountries] = useState<Array<{ code: string; name: string }>>([]);
|
||||
const [countriesLoading, setCountriesLoading] = useState(false);
|
||||
|
||||
// Account settings state
|
||||
const [settings, setSettings] = useState<AccountSettings | null>(null);
|
||||
@@ -53,6 +58,9 @@ export default function AccountSettingsPage() {
|
||||
billing_country: '',
|
||||
tax_id: '',
|
||||
billing_email: '',
|
||||
account_timezone: 'UTC',
|
||||
timezone_mode: 'country' as 'country' | 'manual',
|
||||
timezone_offset: '',
|
||||
});
|
||||
|
||||
// Profile settings state
|
||||
@@ -61,7 +69,6 @@ export default function AccountSettingsPage() {
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
timezone: 'America/New_York',
|
||||
language: 'en',
|
||||
emailNotifications: true,
|
||||
marketingEmails: false,
|
||||
@@ -87,28 +94,26 @@ export default function AccountSettingsPage() {
|
||||
last_name: '',
|
||||
});
|
||||
|
||||
// Load profile from auth store user data
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
const [firstName = '', lastName = ''] = (user.username || '').split(' ');
|
||||
setProfileForm({
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
email: user.email || '',
|
||||
phone: '',
|
||||
timezone: 'America/New_York',
|
||||
language: 'en',
|
||||
emailNotifications: true,
|
||||
marketingEmails: false,
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
loadTeamMembers();
|
||||
loadProfile();
|
||||
loadCountries();
|
||||
}, []);
|
||||
|
||||
// Load profile from auth store user data (fallback if API not ready)
|
||||
useEffect(() => {
|
||||
if (user && !profileForm.email) {
|
||||
const [firstName = '', lastName = ''] = (user.username || '').split(' ');
|
||||
setProfileForm((prev) => ({
|
||||
...prev,
|
||||
firstName,
|
||||
lastName,
|
||||
email: user.email || '',
|
||||
}));
|
||||
}
|
||||
}, [user, profileForm.email]);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
startLoading('Loading settings...');
|
||||
@@ -124,6 +129,9 @@ export default function AccountSettingsPage() {
|
||||
billing_country: accountData.billing_country || '',
|
||||
tax_id: accountData.tax_id || '',
|
||||
billing_email: accountData.billing_email || '',
|
||||
account_timezone: accountData.account_timezone || 'UTC',
|
||||
timezone_mode: accountData.timezone_mode || 'country',
|
||||
timezone_offset: accountData.timezone_offset || '',
|
||||
});
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to load settings');
|
||||
@@ -144,6 +152,36 @@ export default function AccountSettingsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const loadCountries = async () => {
|
||||
try {
|
||||
setCountriesLoading(true);
|
||||
const loadedCountries = await fetchCountries();
|
||||
setCountries(loadedCountries);
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load countries:', err);
|
||||
} finally {
|
||||
setCountriesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const response = await getUserProfile();
|
||||
const profile = response.user;
|
||||
setProfileForm({
|
||||
firstName: profile.first_name || '',
|
||||
lastName: profile.last_name || '',
|
||||
email: profile.email || '',
|
||||
phone: profile.phone || '',
|
||||
language: profile.language || 'en',
|
||||
emailNotifications: profile.email_notifications ?? true,
|
||||
marketingEmails: profile.marketing_emails ?? false,
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load profile:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAccountSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
@@ -154,6 +192,7 @@ export default function AccountSettingsPage() {
|
||||
setSuccess('Account settings updated successfully');
|
||||
toast.success('Account settings saved');
|
||||
await loadData();
|
||||
await refreshUser();
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to update account settings');
|
||||
toast.error(err.message || 'Failed to save settings');
|
||||
@@ -166,8 +205,12 @@ export default function AccountSettingsPage() {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setSavingProfile(true);
|
||||
// Profile data is stored in auth user - refresh after save
|
||||
// Note: Full profile API would go here when backend supports it
|
||||
await updateUserProfile({
|
||||
first_name: profileForm.firstName,
|
||||
last_name: profileForm.lastName,
|
||||
email: profileForm.email,
|
||||
phone: profileForm.phone,
|
||||
});
|
||||
toast.success('Profile settings saved');
|
||||
await refreshUser();
|
||||
} catch (err: any) {
|
||||
@@ -245,6 +288,25 @@ export default function AccountSettingsPage() {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleCountryChange = (value: string) => {
|
||||
const derivedTimezone = getTimezoneForCountry(value);
|
||||
setAccountForm(prev => ({
|
||||
...prev,
|
||||
billing_country: value,
|
||||
account_timezone: prev.timezone_mode === 'country' ? derivedTimezone : prev.account_timezone,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTimezoneModeChange = (value: string) => {
|
||||
const mode = value === 'manual' ? 'manual' : 'country';
|
||||
setAccountForm(prev => ({
|
||||
...prev,
|
||||
timezone_mode: mode,
|
||||
account_timezone: mode === 'country' ? getTimezoneForCountry(prev.billing_country) : prev.account_timezone,
|
||||
timezone_offset: mode === 'country' ? '' : prev.timezone_offset,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
|
||||
@@ -334,13 +396,58 @@ export default function AccountSettingsPage() {
|
||||
value={accountForm.billing_postal_code}
|
||||
onChange={handleAccountChange}
|
||||
/>
|
||||
<InputField
|
||||
type="text"
|
||||
name="billing_country"
|
||||
label="Country"
|
||||
value={accountForm.billing_country}
|
||||
onChange={handleAccountChange}
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Country
|
||||
</label>
|
||||
<Select
|
||||
key={`country-${accountForm.billing_country}`}
|
||||
options={countries.map((country) => ({
|
||||
value: country.code,
|
||||
label: country.name,
|
||||
}))}
|
||||
placeholder={countriesLoading ? 'Loading countries...' : 'Select a country'}
|
||||
defaultValue={accountForm.billing_country}
|
||||
onChange={handleCountryChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Account Timezone (single source)
|
||||
</label>
|
||||
<Select
|
||||
key={`tzmode-${accountForm.timezone_mode}`}
|
||||
options={[
|
||||
{ value: 'country', label: 'Derive from Country' },
|
||||
{ value: 'manual', label: 'Manual UTC Offset' },
|
||||
]}
|
||||
defaultValue={accountForm.timezone_mode}
|
||||
onChange={handleTimezoneModeChange}
|
||||
/>
|
||||
</div>
|
||||
{accountForm.timezone_mode === 'manual' ? (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Manual UTC Offset
|
||||
</label>
|
||||
<Select
|
||||
key={`tzmanual-${accountForm.account_timezone}`}
|
||||
options={TIMEZONE_OPTIONS}
|
||||
defaultValue={accountForm.account_timezone}
|
||||
onChange={(value) => setAccountForm(prev => ({
|
||||
...prev,
|
||||
account_timezone: value,
|
||||
timezone_offset: TIMEZONE_OPTIONS.find(opt => opt.value === value)?.label?.split(' ')[0] || '',
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Timezone derived from country: <span className="font-medium">{accountForm.account_timezone || 'UTC'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -424,24 +531,6 @@ export default function AccountSettingsPage() {
|
||||
<Card className="p-6 border-l-4 border-l-purple-500">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Preferences</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Your Timezone
|
||||
</label>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'America/New_York', label: 'Eastern Time' },
|
||||
{ value: 'America/Chicago', label: 'Central Time' },
|
||||
{ value: 'America/Denver', label: 'Mountain Time' },
|
||||
{ value: 'America/Los_Angeles', label: 'Pacific Time' },
|
||||
{ value: 'UTC', label: 'UTC' },
|
||||
{ value: 'Europe/London', label: 'London' },
|
||||
{ value: 'Asia/Kolkata', label: 'India' },
|
||||
]}
|
||||
defaultValue={profileForm.timezone}
|
||||
onChange={(value) => setProfileForm({ ...profileForm, timezone: value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Language
|
||||
|
||||
Reference in New Issue
Block a user