♻️(front) update calendar API and types
Update API functions and types for subscription management and iCal export. Add fetchApi improvements for error handling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,23 +17,11 @@ function getCSRFToken() {
|
||||
export const SESSION_STORAGE_REDIRECT_AFTER_LOGIN_URL =
|
||||
"redirect_after_login_url";
|
||||
|
||||
const redirect = (url: string, saveRedirectAfterLoginUrl = true) => {
|
||||
if (saveRedirectAfterLoginUrl) {
|
||||
sessionStorage.setItem(
|
||||
SESSION_STORAGE_REDIRECT_AFTER_LOGIN_URL,
|
||||
window.location.href
|
||||
);
|
||||
}
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
export interface fetchAPIOptions {
|
||||
}
|
||||
export type fetchAPIOptions = Record<string, never>;
|
||||
|
||||
export const fetchAPI = async (
|
||||
input: string,
|
||||
init?: RequestInit & { params?: Record<string, string | number> },
|
||||
options?: fetchAPIOptions
|
||||
) => {
|
||||
const apiUrl = new URL(`${baseApiUrl("1.0")}${input}`);
|
||||
if (init?.params) {
|
||||
|
||||
@@ -5,14 +5,24 @@ export interface ApiConfig {
|
||||
FRONTEND_FEEDBACK_BUTTON_IDLE?: boolean;
|
||||
FRONTEND_FEEDBACK_ITEMS?: Record<string, { url: string }>;
|
||||
FRONTEND_MORE_LINK?: string;
|
||||
FRONTEND_FEEDBACK_MESSAGES_WIDGET_ENABLED?: boolean;
|
||||
FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL?: string;
|
||||
FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH?: string;
|
||||
FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL?: string;
|
||||
theme_customization?: ThemeCustomization;
|
||||
}
|
||||
|
||||
export interface ThemeCustomization {
|
||||
footer?: Record<string, unknown>;
|
||||
export interface UserFilters {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ThemeCustomization {
|
||||
footer?: LocalizedRecord;
|
||||
[key: string]: LocalizedRecord | unknown;
|
||||
}
|
||||
|
||||
export interface LocalizedRecord {
|
||||
default?: Record<string, unknown>;
|
||||
[languageCode: string]: Record<string, unknown> | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
export const errorCauses = async (response: Response, data?: unknown) => {
|
||||
const errorsBody = (await response.json()) as Record<
|
||||
string,
|
||||
string | string[]
|
||||
> | null;
|
||||
|
||||
const causes = errorsBody
|
||||
? Object.entries(errorsBody)
|
||||
.map(([, value]) => value)
|
||||
.flat()
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
cause: causes,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
export const getOrigin = () => {
|
||||
return (
|
||||
process.env.NEXT_PUBLIC_API_ORIGIN ||
|
||||
|
||||
@@ -15,20 +15,33 @@ export interface Calendar {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paginated API response.
|
||||
*/
|
||||
interface PaginatedResponse<T> {
|
||||
count: number;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
results: T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all calendars accessible by the current user.
|
||||
*/
|
||||
export const getCalendars = async (): Promise<Calendar[]> => {
|
||||
const response = await fetchAPI("calendars/");
|
||||
return response.json();
|
||||
const data: PaginatedResponse<Calendar> = await response.json();
|
||||
return data.results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new calendar.
|
||||
* Create a new calendar via Django API.
|
||||
* This creates both the CalDAV calendar and the Django record.
|
||||
*/
|
||||
export const createCalendar = async (data: {
|
||||
export const createCalendarApi = async (data: {
|
||||
name: string;
|
||||
color?: string;
|
||||
description?: string;
|
||||
}): Promise<Calendar> => {
|
||||
const response = await fetchAPI("calendars/", {
|
||||
method: "POST",
|
||||
@@ -37,6 +50,29 @@ export const createCalendar = async (data: {
|
||||
return response.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an existing calendar via Django API.
|
||||
*/
|
||||
export const updateCalendarApi = async (
|
||||
calendarId: string,
|
||||
data: { name?: string; color?: string; description?: string }
|
||||
): Promise<Calendar> => {
|
||||
const response = await fetchAPI(`calendars/${calendarId}/`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a calendar via Django API.
|
||||
*/
|
||||
export const deleteCalendarApi = async (calendarId: string): Promise<void> => {
|
||||
await fetchAPI(`calendars/${calendarId}/`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle calendar visibility.
|
||||
*/
|
||||
@@ -49,3 +85,122 @@ export const toggleCalendarVisibility = async (
|
||||
return response.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscription token for iCal export.
|
||||
*/
|
||||
export interface SubscriptionToken {
|
||||
token: string;
|
||||
url: string;
|
||||
caldav_path: string;
|
||||
calendar_name: string;
|
||||
is_active: boolean;
|
||||
last_accessed_at: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for subscription token operations.
|
||||
*/
|
||||
export interface SubscriptionTokenParams {
|
||||
caldavPath: string;
|
||||
calendarName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error types for subscription token operations.
|
||||
*/
|
||||
export type SubscriptionTokenError =
|
||||
| { type: "not_found" }
|
||||
| { type: "permission_denied"; message: string }
|
||||
| { type: "network_error"; message: string }
|
||||
| { type: "server_error"; message: string };
|
||||
|
||||
/**
|
||||
* Result type for getSubscriptionToken - either a token, null (not found), or an error.
|
||||
*/
|
||||
export type GetSubscriptionTokenResult =
|
||||
| { success: true; token: SubscriptionToken | null }
|
||||
| { success: false; error: SubscriptionTokenError };
|
||||
|
||||
/**
|
||||
* Get the subscription token for a calendar by CalDAV path.
|
||||
* Returns a result object with either the token (or null if not found) or an error.
|
||||
*/
|
||||
export const getSubscriptionToken = async (
|
||||
caldavPath: string
|
||||
): Promise<GetSubscriptionTokenResult> => {
|
||||
try {
|
||||
const response = await fetchAPI(
|
||||
`subscription-tokens/by-path/?caldav_path=${encodeURIComponent(caldavPath)}`,
|
||||
{ method: "GET" }
|
||||
);
|
||||
return { success: true, token: await response.json() };
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "status" in error) {
|
||||
const status = error.status as number;
|
||||
// 404 means no token exists yet - this is expected
|
||||
if (status === 404) {
|
||||
return { success: true, token: null };
|
||||
}
|
||||
// Permission denied
|
||||
if (status === 403) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
type: "permission_denied",
|
||||
message: "You don't have access to this calendar",
|
||||
},
|
||||
};
|
||||
}
|
||||
// Server error
|
||||
if (status >= 500) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
type: "server_error",
|
||||
message: "Server error. Please try again later.",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
// Network or unknown error
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
type: "network_error",
|
||||
message: "Network error. Please check your connection.",
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create or get existing subscription token for a calendar.
|
||||
*/
|
||||
export const createSubscriptionToken = async (
|
||||
params: SubscriptionTokenParams
|
||||
): Promise<SubscriptionToken> => {
|
||||
const response = await fetchAPI("subscription-tokens/", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
caldav_path: params.caldavPath,
|
||||
calendar_name: params.calendarName || "",
|
||||
}),
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete (revoke) the subscription token for a calendar.
|
||||
*/
|
||||
export const deleteSubscriptionToken = async (
|
||||
caldavPath: string
|
||||
): Promise<void> => {
|
||||
await fetchAPI(
|
||||
`subscription-tokens/by-path/?caldav_path=${encodeURIComponent(caldavPath)}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user