Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions frontend/src/components/Profile/ProfileCompletionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export const ProfileCompletionModal: React.FC<ProfileCompletionModalProps> = ({
<MapPin className="h-4 w-4" />
{t.cart.profileCompletion.fields.address.label[lang]}
<span className="text-muted-foreground text-xs">
({t.cart.profileCompletion.fields.address.optional[lang]})
{t.cart.profileCompletion.fields.address.optional[lang]}
</span>
</Label>
<p className="text-xs text-muted-foreground">
Expand Down Expand Up @@ -368,7 +368,7 @@ export const ProfileCompletionModal: React.FC<ProfileCompletionModalProps> = ({
</div>
</div>

<DialogFooter className="flex gap-3 pt-4">
<DialogFooter className="flex justify-between gap-3 pt-4">
<Button
type="button"
variant="outline"
Expand All @@ -377,7 +377,12 @@ export const ProfileCompletionModal: React.FC<ProfileCompletionModalProps> = ({
>
{t.cart.profileCompletion.buttons.cancel[lang]}
</Button>
<Button type="submit" disabled={loading} className="flex-1">
<Button
type="submit"
variant="secondary"
disabled={loading}
className="flex-1"
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/hooks/useProfileCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ProfileCompletionStatus {
isComplete: boolean;
hasName: boolean;
hasPhone: boolean;
hasAddress: boolean;
missingFields: string[];
warnings: string[];
}
Expand Down Expand Up @@ -65,25 +66,33 @@ export function useProfileCompletion(): {
profileWithPhone.phone && profileWithPhone.phone.trim()
);

// Check if user has at least one address
const hasAddress = !!(existingAddresses && existingAddresses.length > 0);

const missingFields: string[] = [];
const warnings: string[] = [];

if (!hasName) {
missingFields.push("full_name");
}

if (!hasAddress) {
missingFields.push("address");
}

if (!hasPhone) {
warnings.push(
"Phone number is recommended for easier communication about your bookings",
);
}

const isComplete = hasName; // Only name is required for booking
const isComplete = hasName && hasAddress; // Name and address are required for booking

setStatus({
isComplete,
hasName,
hasPhone,
hasAddress,
missingFields,
warnings,
});
Expand All @@ -93,13 +102,14 @@ export function useProfileCompletion(): {
isComplete: false,
hasName: false,
hasPhone: false,
missingFields: ["full_name"],
hasAddress: false,
missingFields: ["full_name", "address"],
warnings: [],
});
} finally {
setLoading(false);
}
}, [user, userProfile]);
}, [user, userProfile, existingAddresses]);

const updateProfile = useCallback(
async (data: CompleteProfileData): Promise<boolean> => {
Expand Down
100 changes: 83 additions & 17 deletions frontend/src/pages/Cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useLanguage } from "@/context/LanguageContext";
import { useFormattedDate } from "@/hooks/useFormattedDate";
import { useTranslation } from "@/hooks/useTranslation";
import { useProfileCompletion } from "@/hooks/useProfileCompletion";
import { useRoles } from "@/hooks/useRoles";
import {
selectSelectedUser,
getCurrentUser,
Expand All @@ -24,6 +25,8 @@ import { toast } from "sonner";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { toastConfirm } from "../components/ui/toastConfirm";
import { TooltipTrigger, TooltipContent } from "../components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { ProfileCompletionModal } from "../components/Profile/ProfileCompletionModal";
import InlineTimeframePicker from "../components/InlineTimeframeSelector";
import { useAppDispatch, useAppSelector } from "../store/hooks";
Expand Down Expand Up @@ -52,7 +55,10 @@ const Cart: React.FC = () => {
const { formatDate } = useFormattedDate();

// Profile completion hook
const { updateProfile } = useProfileCompletion();
const { status: profileStatus, updateProfile } = useProfileCompletion();

// Roles hook for checking user permissions
const { activeContext } = useRoles();

const [availabilityMap, setAvailabilityMap] = useState<{
[itemId: string]: {
Expand Down Expand Up @@ -126,6 +132,27 @@ const Cart: React.FC = () => {
// Check if items are in different locations
const hasMultipleLocations = itemsByLocation.length > 1;

// Check if checkout should be disabled based on active role context
const isCheckoutDisabled = useMemo(() => {
if (
!activeContext ||
!activeContext.roleName ||
!activeContext.organizationName
) {
return false;
}
if (activeContext.roleName === "super_admin") {
return true;
}
if (
activeContext.roleName === "user" &&
activeContext.organizationName !== "Global"
) {
return true;
}
return false;
}, [activeContext]);

useEffect(() => {
if (!startDate || !endDate || cartItems.length === 0) return;

Expand Down Expand Up @@ -169,6 +196,15 @@ const Cart: React.FC = () => {
});
}, [cartItems, startDate, endDate]);

// Load user addresses for profile completion check
useEffect(() => {
if (user?.id) {
dispatch(getUserAddresses(user.id)).catch((error) => {
console.warn("Failed to load user addresses:", error);
});
}
}, [dispatch, user?.id]);

const handleQuantityChange = (id: string, quantity: number) => {
if (quantity < 1) return;

Expand Down Expand Up @@ -345,6 +381,15 @@ const Cart: React.FC = () => {
}
};

const handleCheckoutClick = () => {
if (isCheckoutDisabled) {
toast.error(t.cart.buttons.wrongRole[lang]);
return;
}

void handleCheckout();
};

const handleCheckout = async () => {
if (!user) {
toast.error(t.cart.toast.loginRequired[lang]);
Expand All @@ -370,6 +415,12 @@ const Cart: React.FC = () => {
return;
}

// Check if profile is complete before proceeding
if (profileStatus && !profileStatus.isComplete) {
setIsProfileModalOpen(true);
return;
}

// Validate all items are within available quantity
const invalidItems = cartItems.filter((item) => {
const availability = availabilityMap[item.item.id];
Expand Down Expand Up @@ -822,23 +873,38 @@ const Cart: React.FC = () => {
</div>

{/* Checkout Button Below Summary */}
<Button
className="bg-background rounded-2xl text-secondary border-secondary border-1 hover:text-background hover:bg-secondary w-full"
disabled={
!startDate || !endDate || bookingLoading || cartItems.length === 0
}
onClick={handleCheckout}
data-cy="cart-checkout-btn"
>
{bookingLoading ? (
<>
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
{t.cart.buttons.processing[lang]}
</>
) : (
t.cart.buttons.checkout[lang]
<TooltipPrimitive.Root>
<TooltipTrigger asChild>
<div className="w-full">
<Button
className="bg-background rounded-2xl text-secondary border-secondary border-1 hover:text-background hover:bg-secondary w-full"
disabled={
!startDate ||
!endDate ||
bookingLoading ||
cartItems.length === 0 ||
isCheckoutDisabled
}
onClick={handleCheckoutClick}
data-cy="cart-checkout-btn"
>
{bookingLoading ? (
<>
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
{t.cart.buttons.processing[lang]}
</>
) : (
t.cart.buttons.checkout[lang]
)}
</Button>
</div>
</TooltipTrigger>
{isCheckoutDisabled && (
<TooltipContent>
<p>{t.cart.buttons.wrongRole[lang]}</p>
</TooltipContent>
)}
</Button>
</TooltipPrimitive.Root>
</div>
</div>

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/translations/modules/cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export const cart = {
fi: "Tuntematon virhe",
en: "Unknown error",
},
wrongRole: {
fi: "Valitse toinen rooli jatkaaksesi kassalle",
en: "Please choose another role to proceed with checkout",
},
},
toast: {
clearCartTitle: {
Expand Down
Loading