nielsr HF staff commited on
Commit
71b5770
·
1 Parent(s): 61b7e79

Update dialog

Browse files
Files changed (1) hide show
  1. src/components/ConferenceDialog.tsx +63 -68
src/components/ConferenceDialog.tsx CHANGED
@@ -15,6 +15,7 @@ import {
15
  DropdownMenuItem,
16
  DropdownMenuTrigger,
17
  } from "@/components/ui/dropdown-menu";
 
18
 
19
  interface ConferenceDialogProps {
20
  conference: Conference;
@@ -24,7 +25,40 @@ interface ConferenceDialogProps {
24
 
25
  const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogProps) => {
26
  const deadlineDate = conference.deadline && conference.deadline !== 'TBD' ? parseISO(conference.deadline) : null;
27
- const daysLeft = deadlineDate && isValid(deadlineDate) ? formatDistanceToNow(deadlineDate, { addSuffix: true }) : 'TBD';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  const getCountdownColor = () => {
30
  if (!deadlineDate || !isValid(deadlineDate)) return "text-neutral-600";
@@ -59,82 +93,36 @@ const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogPr
59
 
60
  const createCalendarEvent = (type: 'google' | 'apple') => {
61
  try {
62
- let startDate: Date;
63
- let endDate: Date;
64
-
65
- // Primary: Use start and end fields
66
- if (conference.start && conference.end) {
67
- // Check if the dates are Date objects using a type guard
68
- const isDate = (value: any): value is Date => {
69
- return value && Object.prototype.toString.call(value) === '[object Date]';
70
- };
71
-
72
- if (isDate(conference.start) && isDate(conference.end)) {
73
- startDate = conference.start;
74
- endDate = conference.end;
75
- } else {
76
- // Otherwise, parse the string dates
77
- startDate = parseISO(String(conference.start));
78
- endDate = parseISO(String(conference.end));
79
- }
80
-
81
- // Validate parsed dates
82
- if (!isValid(startDate) || !isValid(endDate)) {
83
- throw new Error('Invalid conference dates');
84
- }
85
-
86
- // Add one day to end date to include the full last day
87
- endDate = addDays(endDate, 1);
88
- }
89
- // Fallback: Parse from date field
90
- else if (conference.date && typeof conference.date === 'string') {
91
- const [monthDay, year] = conference.date.split(', ');
92
- const [month, days] = monthDay.split(' ');
93
- const [startDay, endDay] = days.split(/[-–]/);
94
-
95
- try {
96
- startDate = parse(`${month} ${startDay} ${year}`, 'MMMM d yyyy', new Date()) ||
97
- parse(`${month} ${startDay} ${year}`, 'MMM d yyyy', new Date());
98
-
99
- if (endDay) {
100
- endDate = parse(`${month} ${endDay} ${year}`, 'MMMM d yyyy', new Date()) ||
101
- parse(`${month} ${endDay} ${year}`, 'MMM d yyyy', new Date());
102
- // Add one day to end date to include the full last day
103
- endDate = addDays(endDate, 1);
104
- } else {
105
- // For single-day conferences
106
- startDate = startDate || new Date();
107
- endDate = addDays(startDate, 1);
108
- }
109
- } catch (parseError) {
110
- throw new Error('Invalid date format');
111
- }
112
- } else {
113
- throw new Error('No valid date information found');
114
  }
115
 
116
- // Validate dates
117
- if (!isValid(startDate) || !isValid(endDate)) {
118
- throw new Error('Invalid conference dates');
 
119
  }
120
 
121
- const formatDateForGoogle = (date: Date) => format(date, "yyyyMMdd");
122
- const formatDateForApple = (date: Date) => format(date, "yyyyMMdd'T'HHmmss");
 
 
 
123
 
124
- const title = encodeURIComponent(conference.title);
125
  const location = encodeURIComponent(conference.place);
126
  const description = encodeURIComponent(
127
- `Conference: ${conference.full_name || conference.title}\n` +
128
- `Location: ${conference.place}\n` +
129
- `Deadline: ${conference.deadline}\n` +
130
  (conference.abstract_deadline ? `Abstract Deadline: ${conference.abstract_deadline}\n` : '') +
 
 
131
  (conference.link ? `Website: ${conference.link}` : '')
132
  );
133
 
134
  if (type === 'google') {
135
  const url = `https://calendar.google.com/calendar/render?action=TEMPLATE` +
136
  `&text=${title}` +
137
- `&dates=${formatDateForGoogle(startDate)}/${formatDateForGoogle(endDate)}` +
138
  `&details=${description}` +
139
  `&location=${location}` +
140
  `&sprop=website:${encodeURIComponent(conference.link || '')}`;
@@ -144,7 +132,7 @@ const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogPr
144
  VERSION:2.0
145
  BEGIN:VEVENT
146
  URL:${conference.link || ''}
147
- DTSTART:${formatDateForApple(startDate)}
148
  DTEND:${formatDateForApple(endDate)}
149
  SUMMARY:${title}
150
  DESCRIPTION:${description}
@@ -154,7 +142,7 @@ END:VCALENDAR`;
154
 
155
  const link = document.createElement('a');
156
  link.href = url;
157
- link.download = `${conference.title.toLowerCase().replace(/\s+/g, '-')}.ics`;
158
  document.body.appendChild(link);
159
  link.click();
160
  document.body.removeChild(link);
@@ -226,9 +214,16 @@ END:VCALENDAR`;
226
 
227
  <div className="flex items-center">
228
  <AlarmClock className={`h-5 w-5 mr-3 flex-shrink-0 ${getCountdownColor()}`} />
229
- <span className={`font-medium ${getCountdownColor()}`}>
230
- {daysLeft}
231
- </span>
 
 
 
 
 
 
 
232
  </div>
233
 
234
  {Array.isArray(conference.tags) && conference.tags.length > 0 && (
 
15
  DropdownMenuItem,
16
  DropdownMenuTrigger,
17
  } from "@/components/ui/dropdown-menu";
18
+ import { useState, useEffect } from "react";
19
 
20
  interface ConferenceDialogProps {
21
  conference: Conference;
 
25
 
26
  const ConferenceDialog = ({ conference, open, onOpenChange }: ConferenceDialogProps) => {
27
  const deadlineDate = conference.deadline && conference.deadline !== 'TBD' ? parseISO(conference.deadline) : null;
28
+ const [countdown, setCountdown] = useState<string>('');
29
+
30
+ useEffect(() => {
31
+ const calculateTimeLeft = () => {
32
+ if (!deadlineDate || !isValid(deadlineDate)) {
33
+ setCountdown('TBD');
34
+ return;
35
+ }
36
+
37
+ const now = new Date().getTime();
38
+ const difference = deadlineDate.getTime() - now;
39
+
40
+ if (difference <= 0) {
41
+ setCountdown('Deadline passed');
42
+ return;
43
+ }
44
+
45
+ const days = Math.floor(difference / (1000 * 60 * 60 * 24));
46
+ const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
47
+ const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
48
+ const seconds = Math.floor((difference % (1000 * 60)) / 1000);
49
+
50
+ setCountdown(`${days}d ${hours}h ${minutes}m ${seconds}s`);
51
+ };
52
+
53
+ // Calculate immediately
54
+ calculateTimeLeft();
55
+
56
+ // Update every second
57
+ const timer = setInterval(calculateTimeLeft, 1000);
58
+
59
+ // Cleanup interval on component unmount
60
+ return () => clearInterval(timer);
61
+ }, [deadlineDate]);
62
 
63
  const getCountdownColor = () => {
64
  if (!deadlineDate || !isValid(deadlineDate)) return "text-neutral-600";
 
93
 
94
  const createCalendarEvent = (type: 'google' | 'apple') => {
95
  try {
96
+ if (!conference.deadline || conference.deadline === 'TBD') {
97
+ throw new Error('No valid deadline found');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  }
99
 
100
+ // Parse the deadline date
101
+ const deadlineDate = parseISO(conference.deadline);
102
+ if (!isValid(deadlineDate)) {
103
+ throw new Error('Invalid deadline date');
104
  }
105
 
106
+ // Create an end date 1 hour after the deadline
107
+ const endDate = new Date(deadlineDate.getTime() + (60 * 60 * 1000));
108
+
109
+ const formatDateForGoogle = (date: Date) => format(date, "yyyyMMdd'T'HHmmss'Z'");
110
+ const formatDateForApple = (date: Date) => format(date, "yyyyMMdd'T'HHmmss'Z'");
111
 
112
+ const title = encodeURIComponent(`${conference.title} deadline`);
113
  const location = encodeURIComponent(conference.place);
114
  const description = encodeURIComponent(
115
+ `Paper Submission Deadline for ${conference.full_name || conference.title}\n` +
 
 
116
  (conference.abstract_deadline ? `Abstract Deadline: ${conference.abstract_deadline}\n` : '') +
117
+ `Conference Dates: ${conference.date}\n` +
118
+ `Location: ${conference.place}\n` +
119
  (conference.link ? `Website: ${conference.link}` : '')
120
  );
121
 
122
  if (type === 'google') {
123
  const url = `https://calendar.google.com/calendar/render?action=TEMPLATE` +
124
  `&text=${title}` +
125
+ `&dates=${formatDateForGoogle(deadlineDate)}/${formatDateForGoogle(endDate)}` +
126
  `&details=${description}` +
127
  `&location=${location}` +
128
  `&sprop=website:${encodeURIComponent(conference.link || '')}`;
 
132
  VERSION:2.0
133
  BEGIN:VEVENT
134
  URL:${conference.link || ''}
135
+ DTSTART:${formatDateForApple(deadlineDate)}
136
  DTEND:${formatDateForApple(endDate)}
137
  SUMMARY:${title}
138
  DESCRIPTION:${description}
 
142
 
143
  const link = document.createElement('a');
144
  link.href = url;
145
+ link.download = `${conference.title.toLowerCase().replace(/\s+/g, '-')}-deadline.ics`;
146
  document.body.appendChild(link);
147
  link.click();
148
  document.body.removeChild(link);
 
214
 
215
  <div className="flex items-center">
216
  <AlarmClock className={`h-5 w-5 mr-3 flex-shrink-0 ${getCountdownColor()}`} />
217
+ <div>
218
+ <span className={`font-medium ${getCountdownColor()}`}>
219
+ {countdown}
220
+ </span>
221
+ {deadlineDate && isValid(deadlineDate) && (
222
+ <div className="text-sm text-neutral-500">
223
+ {format(deadlineDate, "MMMM d, yyyy 'at' HH:mm:ss")} {conference.timezone}
224
+ </div>
225
+ )}
226
+ </div>
227
  </div>
228
 
229
  {Array.isArray(conference.tags) && conference.tags.length > 0 && (