CalDAV: Support Basic auth; trim+validate UUID; deprecate query token via headers; ICS end time helper; docs+instructions updated
This commit is contained in:
@@ -24,12 +24,12 @@ export function AdminAvailability() {
|
||||
// Neue Queries für wiederkehrende Verfügbarkeiten (mit Authentifizierung)
|
||||
const { data: recurringRules, refetch: refetchRecurringRules } = useQuery(
|
||||
queryClient.recurringAvailability.live.adminListRules.experimental_liveOptions({
|
||||
input: { sessionId: localStorage.getItem("sessionId") || "" }
|
||||
input: {}
|
||||
})
|
||||
);
|
||||
const { data: timeOffPeriods } = useQuery(
|
||||
queryClient.recurringAvailability.live.adminListTimeOff.experimental_liveOptions({
|
||||
input: { sessionId: localStorage.getItem("sessionId") || "" }
|
||||
input: {}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -177,14 +177,9 @@ export function AdminAvailability() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = localStorage.getItem("sessionId") || "";
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
|
||||
createRule(
|
||||
{ sessionId, dayOfWeek: selectedDayOfWeek, startTime: ruleStartTime, endTime: ruleEndTime },
|
||||
{ dayOfWeek: selectedDayOfWeek, startTime: ruleStartTime, endTime: ruleEndTime },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg(`Regel für ${getDayName(selectedDayOfWeek)} erstellt.`);
|
||||
@@ -233,13 +228,8 @@ export function AdminAvailability() {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
toggleRuleActive(
|
||||
{ sessionId, id: rule.id },
|
||||
{ id: rule.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg(`Regel ${rule.isActive ? "deaktiviert" : "aktiviert"}.`);
|
||||
@@ -256,13 +246,8 @@ export function AdminAvailability() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
deleteRule(
|
||||
{ sessionId, id: rule.id },
|
||||
{ id: rule.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg("Regel gelöscht.");
|
||||
@@ -348,14 +333,9 @@ export function AdminAvailability() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = localStorage.getItem("sessionId") || "";
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
|
||||
createTimeOff(
|
||||
{ sessionId, startDate: timeOffStartDate, endDate: timeOffEndDate, reason: timeOffReason },
|
||||
{ startDate: timeOffStartDate, endDate: timeOffEndDate, reason: timeOffReason },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg("Urlaubszeit hinzugefügt.");
|
||||
@@ -415,13 +395,8 @@ export function AdminAvailability() {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
deleteTimeOff(
|
||||
{ sessionId, id: period.id },
|
||||
{ id: period.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg("Urlaubszeit gelöscht.");
|
||||
|
@@ -255,7 +255,7 @@ export function AdminBookings() {
|
||||
{booking.status === "pending" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => updateBookingStatus({ sessionId: localStorage.getItem("sessionId") || "", id: booking.id, status: "confirmed" })}
|
||||
onClick={() => updateBookingStatus({ id: booking.id, status: "confirmed" })}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
Confirm
|
||||
@@ -271,7 +271,7 @@ export function AdminBookings() {
|
||||
{booking.status === "confirmed" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => updateBookingStatus({ sessionId: localStorage.getItem("sessionId") || "", id: booking.id, status: "completed" })}
|
||||
onClick={() => updateBookingStatus({ id: booking.id, status: "completed" })}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
Complete
|
||||
@@ -286,7 +286,7 @@ export function AdminBookings() {
|
||||
)}
|
||||
{(booking.status === "cancelled" || booking.status === "completed") && (
|
||||
<button
|
||||
onClick={() => updateBookingStatus({ sessionId: localStorage.getItem("sessionId") || "", id: booking.id, status: "confirmed" })}
|
||||
onClick={() => updateBookingStatus({ id: booking.id, status: "confirmed" })}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
Reactivate
|
||||
@@ -352,8 +352,7 @@ export function AdminBookings() {
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
const sessionId = localStorage.getItem("sessionId") || "";
|
||||
updateBookingStatus({ sessionId, id: showCancelConfirm, status: "cancelled" });
|
||||
updateBookingStatus({ id: showCancelConfirm, status: "cancelled" });
|
||||
}}
|
||||
className="flex-1 bg-red-600 text-white py-2 px-4 rounded-md hover:bg-red-700 transition-colors"
|
||||
>
|
||||
|
@@ -164,24 +164,18 @@ export function AdminCalendar() {
|
||||
};
|
||||
|
||||
const handleStatusUpdate = (bookingId: string, newStatus: string) => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId) return;
|
||||
|
||||
updateBookingStatus({
|
||||
sessionId,
|
||||
id: bookingId,
|
||||
status: newStatus as "pending" | "confirmed" | "cancelled" | "completed"
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteBooking = () => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId || !showDeleteConfirm) return;
|
||||
if (!showDeleteConfirm) return;
|
||||
|
||||
if (deleteActionType === 'cancel') {
|
||||
// For cancel action, use updateStatus instead of remove
|
||||
updateBookingStatus({
|
||||
sessionId,
|
||||
id: showDeleteConfirm,
|
||||
status: "cancelled"
|
||||
}, {
|
||||
@@ -197,7 +191,6 @@ export function AdminCalendar() {
|
||||
} else {
|
||||
// For delete action, use remove with email option
|
||||
removeBooking({
|
||||
sessionId,
|
||||
id: showDeleteConfirm,
|
||||
sendEmail: sendDeleteEmail,
|
||||
}, {
|
||||
@@ -216,11 +209,8 @@ export function AdminCalendar() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
const handleCreateBooking = () => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId) return;
|
||||
|
||||
createManualBooking({
|
||||
sessionId,
|
||||
...createFormData
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
@@ -262,13 +252,11 @@ export function AdminCalendar() {
|
||||
};
|
||||
|
||||
const handleRescheduleBooking = () => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId || !showRescheduleModal) return;
|
||||
if (!showRescheduleModal) return;
|
||||
const booking = bookings?.find(b => b.id === showRescheduleModal);
|
||||
if (!booking) return;
|
||||
|
||||
proposeReschedule({
|
||||
sessionId,
|
||||
bookingId: booking.id,
|
||||
proposedDate: rescheduleFormData.appointmentDate,
|
||||
proposedTime: rescheduleFormData.appointmentTime,
|
||||
@@ -285,11 +273,8 @@ export function AdminCalendar() {
|
||||
};
|
||||
|
||||
const handleGenerateCalDAVToken = () => {
|
||||
const sessionId = localStorage.getItem('sessionId');
|
||||
if (!sessionId) return;
|
||||
|
||||
generateCalDAVToken({
|
||||
sessionId
|
||||
}, {
|
||||
onSuccess: (data) => {
|
||||
setCaldavData(data);
|
||||
|
@@ -14,7 +14,7 @@ export function AdminGallery() {
|
||||
// Data fetching with live query
|
||||
const { data: photos, refetch: refetchPhotos } = useQuery(
|
||||
queryClient.gallery.live.adminListPhotos.experimental_liveOptions({
|
||||
input: { sessionId: localStorage.getItem("sessionId") || "" }
|
||||
input: {}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -166,12 +166,6 @@ export function AdminGallery() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
setDraggedPhotoId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a fully reordered list based on the current sorted order
|
||||
const sorted = [...(photos || [])].sort((a, b) => a.order - b.order);
|
||||
@@ -191,7 +185,6 @@ export function AdminGallery() {
|
||||
|
||||
updatePhotoOrder(
|
||||
{
|
||||
sessionId,
|
||||
photoOrders
|
||||
},
|
||||
{
|
||||
@@ -304,15 +297,9 @@ export function AdminGallery() {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = localStorage.getItem("sessionId") || "";
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
|
||||
uploadPhoto(
|
||||
{
|
||||
sessionId,
|
||||
base64Data: photoBase64,
|
||||
title: photoTitle || undefined
|
||||
},
|
||||
@@ -396,13 +383,8 @@ export function AdminGallery() {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm("Möchtest du dieses Foto wirklich löschen?")) {
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
deletePhoto(
|
||||
{ sessionId, id: photo.id },
|
||||
{ id: photo.id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg("Foto gelöscht.");
|
||||
@@ -425,13 +407,8 @@ export function AdminGallery() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const sessionId = localStorage.getItem("sessionId");
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
setCoverPhoto(
|
||||
{ sessionId, id: photo.id },
|
||||
{ id: photo.id },
|
||||
{
|
||||
onSuccess: () => setSuccessMsg("Als Cover-Bild gesetzt."),
|
||||
onError: (err: any) => setErrorMsg(err?.message || "Fehler beim Setzen des Cover-Bildes."),
|
||||
|
@@ -91,18 +91,17 @@ export function AdminReviews() {
|
||||
}
|
||||
}, [successMsg]);
|
||||
|
||||
const sessionId = typeof window !== "undefined" ? localStorage.getItem("sessionId") || "" : "";
|
||||
|
||||
const { data: reviews } = useQuery(
|
||||
queryClient.reviews.live.adminListReviews.experimental_liveOptions({
|
||||
input: { sessionId, statusFilter: activeStatusTab },
|
||||
input: { statusFilter: activeStatusTab },
|
||||
})
|
||||
);
|
||||
|
||||
// Separate queries for quick stats calculation
|
||||
const { data: allReviews } = useQuery(
|
||||
queryClient.reviews.live.adminListReviews.experimental_liveOptions({
|
||||
input: { sessionId },
|
||||
input: {},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -266,13 +265,13 @@ export function AdminReviews() {
|
||||
{review.status === "pending" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => approveReview({ sessionId, id: review.id })}
|
||||
onClick={() => approveReview({ id: review.id })}
|
||||
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1.5 rounded-md text-sm"
|
||||
>
|
||||
Genehmigen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => rejectReview({ sessionId, id: review.id })}
|
||||
onClick={() => rejectReview({ id: review.id })}
|
||||
className="bg-red-600 hover:bg-red-700 text-white px-3 py-1.5 rounded-md text-sm"
|
||||
>
|
||||
Ablehnen
|
||||
@@ -289,7 +288,7 @@ export function AdminReviews() {
|
||||
{review.status === "approved" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => rejectReview({ sessionId, id: review.id })}
|
||||
onClick={() => rejectReview({ id: review.id })}
|
||||
className="bg-red-600 hover:bg-red-700 text-white px-3 py-1.5 rounded-md text-sm"
|
||||
>
|
||||
Ablehnen
|
||||
@@ -306,7 +305,7 @@ export function AdminReviews() {
|
||||
{review.status === "rejected" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => approveReview({ sessionId, id: review.id })}
|
||||
onClick={() => approveReview({ id: review.id })}
|
||||
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1.5 rounded-md text-sm"
|
||||
>
|
||||
Genehmigen
|
||||
@@ -334,7 +333,7 @@ export function AdminReviews() {
|
||||
</p>
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
onClick={() => deleteReview({ sessionId, id: showDeleteConfirm })}
|
||||
onClick={() => deleteReview({ id: showDeleteConfirm })}
|
||||
className="flex-1 bg-red-600 text-white py-2 px-4 rounded-md hover:bg-red-700 transition-colors"
|
||||
>
|
||||
Ja, löschen
|
||||
|
@@ -11,7 +11,6 @@ interface User {
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
sessionId: string | null;
|
||||
isLoading: boolean;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
@@ -34,7 +33,6 @@ interface AuthProviderProps {
|
||||
|
||||
export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const { mutateAsync: loginMutation } = useMutation(
|
||||
@@ -50,56 +48,45 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Check for existing session on app load
|
||||
const storedSessionId = localStorage.getItem("sessionId");
|
||||
if (storedSessionId) {
|
||||
verifySessionMutation(storedSessionId)
|
||||
.then((result) => {
|
||||
setUser(result.user);
|
||||
setSessionId(storedSessionId);
|
||||
})
|
||||
.catch(() => {
|
||||
localStorage.removeItem("sessionId");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
// Check for existing session on app load - session comes from cookies
|
||||
verifySessionMutation({})
|
||||
.then((result) => {
|
||||
setUser(result.user);
|
||||
})
|
||||
.catch(() => {
|
||||
// Session invalid or expired - user remains null
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [verifySessionMutation]);
|
||||
|
||||
const login = async (username: string, password: string) => {
|
||||
try {
|
||||
const result = await loginMutation({ username, password });
|
||||
setUser(result.user);
|
||||
setSessionId(result.sessionId);
|
||||
localStorage.setItem("sessionId", result.sessionId);
|
||||
// Cookies are set automatically by the server
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
if (sessionId) {
|
||||
try {
|
||||
await logoutMutation(sessionId);
|
||||
} catch (error) {
|
||||
// Continue with logout even if server call fails
|
||||
console.error("Logout error:", error);
|
||||
}
|
||||
try {
|
||||
await logoutMutation({});
|
||||
// Cookies are cleared automatically by the server
|
||||
} catch (error) {
|
||||
// Continue with logout even if server call fails
|
||||
console.error("Logout error:", error);
|
||||
}
|
||||
|
||||
setUser(null);
|
||||
setSessionId(null);
|
||||
localStorage.removeItem("sessionId");
|
||||
};
|
||||
|
||||
const isOwner = user?.role === "owner";
|
||||
|
||||
const value: AuthContextType = {
|
||||
user,
|
||||
sessionId,
|
||||
isLoading,
|
||||
login,
|
||||
logout,
|
||||
|
@@ -4,7 +4,7 @@ import { useAuth } from "@/client/components/auth-provider";
|
||||
import { queryClient } from "@/client/rpc-client";
|
||||
|
||||
export function UserProfile() {
|
||||
const { user, sessionId, logout } = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const [showPasswordChange, setShowPasswordChange] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
@@ -31,13 +31,7 @@ export function UserProfile() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
setError("Keine gültige Sitzung");
|
||||
return;
|
||||
}
|
||||
|
||||
changePassword({
|
||||
sessionId,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
}, {
|
||||
|
Reference in New Issue
Block a user