Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions assets/core/scss/components/_tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
text-align: center;
pointer-events: none;

&-medium {
max-width: 226px;
padding: $tutor-spacing-5;
}

&-large {
max-width: 320px;
padding: $tutor-spacing-5;
Expand Down
87 changes: 82 additions & 5 deletions assets/core/ts/components/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const PLACEMENTS = {
BOTTOM_START: 'bottom-start',
BOTTOM_END: 'bottom-end',
LEFT: 'left',
LEFT_TOP: 'left-top',
LEFT_BOTTOM: 'left-bottom',
RIGHT: 'right',
RIGHT_TOP: 'right-top',
RIGHT_BOTTOM: 'right-bottom',
} as const;

export interface PopoverProps {
Expand Down Expand Up @@ -82,7 +86,11 @@ export const popover = (props: PopoverProps = {}) => ({

const rtlAdaptations: Record<string, string> = {
[PLACEMENTS.LEFT]: PLACEMENTS.RIGHT,
[PLACEMENTS.LEFT_TOP]: PLACEMENTS.RIGHT_TOP,
[PLACEMENTS.LEFT_BOTTOM]: PLACEMENTS.RIGHT_BOTTOM,
[PLACEMENTS.RIGHT]: PLACEMENTS.LEFT,
[PLACEMENTS.RIGHT_TOP]: PLACEMENTS.LEFT_TOP,
[PLACEMENTS.RIGHT_BOTTOM]: PLACEMENTS.LEFT_BOTTOM,
[PLACEMENTS.TOP_START]: PLACEMENTS.TOP_END,
[PLACEMENTS.TOP_END]: PLACEMENTS.TOP_START,
[PLACEMENTS.BOTTOM_START]: PLACEMENTS.BOTTOM_END,
Expand Down Expand Up @@ -164,7 +172,8 @@ export const popover = (props: PopoverProps = {}) => ({
};

const placement = this.resolvePlacement(this.actualPlacement, triggerRect, contentRect, viewport);
const { top, left } = this.calculatePosition(triggerRect, contentRect, placement);
const viewportPosition = this.calculatePosition(triggerRect, contentRect, placement);
const { top, left } = this.convertViewportPositionToContentPosition(content, viewportPosition);

// Apply positioning
content.style.position = 'fixed';
Expand Down Expand Up @@ -207,12 +216,12 @@ export const popover = (props: PopoverProps = {}) => ({
return placement.replace('bottom', 'top');
}

if (placement === PLACEMENTS.LEFT && needsHorizontalFlip.left) {
return PLACEMENTS.RIGHT;
if (placement.startsWith('left') && needsHorizontalFlip.left) {
return placement.replace('left', 'right');
}

if (placement === PLACEMENTS.RIGHT && needsHorizontalFlip.right) {
return PLACEMENTS.LEFT;
if (placement.startsWith('right') && needsHorizontalFlip.right) {
return placement.replace('right', 'left');
}

return placement;
Expand Down Expand Up @@ -251,15 +260,83 @@ export const popover = (props: PopoverProps = {}) => ({
top = triggerRect.top + (triggerRect.height - contentRect.height) / 2;
left = triggerRect.left - contentRect.width - this.offset;
break;
case PLACEMENTS.LEFT_TOP:
top = triggerRect.top;
left = triggerRect.left - contentRect.width - this.offset;
break;
case PLACEMENTS.LEFT_BOTTOM:
top = triggerRect.bottom - contentRect.height;
left = triggerRect.left - contentRect.width - this.offset;
break;
case PLACEMENTS.RIGHT:
top = triggerRect.top + (triggerRect.height - contentRect.height) / 2;
left = triggerRect.right + this.offset;
break;
case PLACEMENTS.RIGHT_TOP:
top = triggerRect.top;
left = triggerRect.right + this.offset;
break;
case PLACEMENTS.RIGHT_BOTTOM:
top = triggerRect.bottom - contentRect.height;
left = triggerRect.right + this.offset;
break;
}

return { top, left };
},

convertViewportPositionToContentPosition(content: HTMLElement, position: { top: number; left: number }) {
const containingBlock = this.getFixedContainingBlock(content);

if (!containingBlock) {
return position;
}

const containingBlockRect = containingBlock.getBoundingClientRect();
const scaleX = containingBlock.offsetWidth ? containingBlockRect.width / containingBlock.offsetWidth || 1 : 1;
const scaleY = containingBlock.offsetHeight ? containingBlockRect.height / containingBlock.offsetHeight || 1 : 1;

return {
top: (position.top - containingBlockRect.top) / scaleY - containingBlock.clientTop,
left: (position.left - containingBlockRect.left) / scaleX - containingBlock.clientLeft,
};
},

getFixedContainingBlock(element: HTMLElement) {
let parent = element.parentElement;

while (parent && parent !== document.documentElement) {
if (this.createsFixedContainingBlock(parent)) {
return parent;
}

parent = parent.parentElement;
}

return null;
},

createsFixedContainingBlock(element: HTMLElement) {
const style = window.getComputedStyle(element);
const willChangeProperties = style.willChange.split(',').map((property) => property.trim());
const containProperties = style.contain.split(' ');
const backdropFilter =
style.getPropertyValue('backdrop-filter') || style.getPropertyValue('-webkit-backdrop-filter');
const contentVisibility = style.getPropertyValue('content-visibility');
const containerType = style.getPropertyValue('container-type');

return (
style.transform !== 'none' ||
style.perspective !== 'none' ||
style.filter !== 'none' ||
(backdropFilter !== '' && backdropFilter !== 'none') ||
contentVisibility === 'auto' ||
(containerType !== '' && containerType !== 'normal') ||
willChangeProperties.some((property) => ['transform', 'perspective', 'filter'].includes(property)) ||
containProperties.some((property) => ['layout', 'paint', 'strict', 'content'].includes(property))
);
},

updatePlacementClasses(content: HTMLElement, placement: string) {
// Remove all placement classes
const placementClasses = ['tutor-popover-top', 'tutor-popover-bottom', 'tutor-popover-left', 'tutor-popover-right'];
Expand Down
7 changes: 5 additions & 2 deletions assets/core/ts/components/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const TOOLTIP_TRIGGERS = {

const TOOLTIP_SIZES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
} as const;

Expand Down Expand Up @@ -328,14 +329,16 @@ export const tooltip = (props: TooltipProps = {}) => {
'tutor-tooltip-start',
'tutor-tooltip-end',
];
const sizeClasses = ['tutor-tooltip-large'];
const sizeClasses = ['tutor-tooltip-medium', 'tutor-tooltip-large'];
const arrowClasses = ['tutor-tooltip-arrow-start', 'tutor-tooltip-arrow-center', 'tutor-tooltip-arrow-end'];

content.classList.remove(...placementClasses, ...sizeClasses, ...arrowClasses);

content.classList.add(`tutor-tooltip-${placement}`);

if (this.size === TOOLTIP_SIZES.LARGE) {
if (this.size === TOOLTIP_SIZES.MEDIUM) {
content.classList.add('tutor-tooltip-medium');
} else if (this.size === TOOLTIP_SIZES.LARGE) {
content.classList.add('tutor-tooltip-large');
}

Expand Down
1 change: 1 addition & 0 deletions assets/icons/camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 52 additions & 9 deletions assets/src/js/frontend/dashboard/pages/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import endpoints from '@TutorShared/utils/endpoints';
import { type TutorMutationResponse } from '@TutorShared/utils/types';
import { convertToErrorMessage } from '@TutorShared/utils/util';

interface ProfilePhotoFormProps {
interface UserPhotoFormProps {
photo_file: File;
photo_type: 'profile_photo';
photo_type: 'profile_photo' | 'cover_photo';
}

interface RemoveUserPhotoProps {
photo_type: UserPhotoFormProps['photo_type'];
}

interface AccountFormProps {
Expand All @@ -21,6 +25,8 @@ interface AccountFormProps {
occupation: string;
bio: string;
display_name: string;
profile_photo: string;
cover_photo: string;
tutor_pro_custom_signature_id: WPMedia | null;
}

Expand Down Expand Up @@ -89,6 +95,8 @@ const settings = () => {
$el: null as HTMLElement | null,
uploadProfilePhotoMutation: null as MutationState<TutorMutationResponse<string>> | null,
removeProfilePhotoMutation: null as MutationState<TutorMutationResponse<string>> | null,
uploadCoverPhotoMutation: null as MutationState<TutorMutationResponse<string>> | null,
removeCoverPhotoMutation: null as MutationState<TutorMutationResponse<string>> | null,
updateProfileMutation: null as MutationState<TutorMutationResponse<string>> | null,
saveSocialProfileMutation: null as MutationState<TutorMutationResponse<string>> | null,
saveBillingInfoMutation: null as MutationState<TutorMutationResponse<string>> | null,
Expand All @@ -106,6 +114,8 @@ const settings = () => {
this.handleUpdateProfile = this.handleUpdateProfile.bind(this);
this.handleUploadProfilePhoto = this.handleUploadProfilePhoto.bind(this);
this.handleRemoveProfilePhoto = this.handleRemoveProfilePhoto.bind(this);
this.handleUploadCoverPhoto = this.handleUploadCoverPhoto.bind(this);
this.handleRemoveCoverPhoto = this.handleRemoveCoverPhoto.bind(this);
this.handleSaveSocialProfile = this.handleSaveSocialProfile.bind(this);
this.handleSaveBillingInfo = this.handleSaveBillingInfo.bind(this);
this.handleSaveWithdrawMethod = this.handleSaveWithdrawMethod.bind(this);
Expand All @@ -122,7 +132,7 @@ const settings = () => {
},
});

this.uploadProfilePhotoMutation = query.useMutation(this.uploadProfilePhoto, {
this.uploadProfilePhotoMutation = query.useMutation(this.uploadUserPhoto, {
onSuccess: () => {
toast.success(__('Successfully updated profile photo.', 'tutor'));
},
Expand All @@ -131,7 +141,7 @@ const settings = () => {
},
});

this.removeProfilePhotoMutation = query.useMutation(this.removeProfilePhoto, {
this.removeProfilePhotoMutation = query.useMutation(this.removeUserPhoto, {
onSuccess: () => {
toast.success(__('Successfully removed profile photo.', 'tutor'));
},
Expand All @@ -140,6 +150,24 @@ const settings = () => {
},
});

this.uploadCoverPhotoMutation = query.useMutation(this.uploadUserPhoto, {
onSuccess: () => {
toast.success(__('Successfully updated cover photo.', 'tutor'));
},
onError: (error: Error) => {
toast.error(convertToErrorMessage(error));
},
});

this.removeCoverPhotoMutation = query.useMutation(this.removeUserPhoto, {
onSuccess: () => {
toast.success(__('Successfully removed cover photo.', 'tutor'));
},
onError: (error: Error) => {
toast.error(convertToErrorMessage(error));
},
});

this.updateProfileMutation = query.useMutation(this.updateProfile, {
onSuccess: (data: TutorMutationResponse<string>) => {
toast.success(data?.message ?? __('Successfully updated profile', 'tutor'));
Expand Down Expand Up @@ -271,7 +299,7 @@ const settings = () => {
return wpAjaxInstance.post(endpoints.UPDATE_PROFILE_NOTIFICATION, transformedPayload).then((res) => res.data);
},

async uploadProfilePhoto(payload: ProfilePhotoFormProps) {
async uploadUserPhoto(payload: UserPhotoFormProps) {
return wpAjaxInstance.post(endpoints.UPLOAD_PROFILE_PHOTO, payload).then((res) => res.data);
},

Expand All @@ -282,16 +310,31 @@ const settings = () => {
const data = {
photo_file: files[0],
photo_type: 'profile_photo',
} satisfies ProfilePhotoFormProps;
} satisfies UserPhotoFormProps;
await this.uploadProfilePhotoMutation?.mutate(data);
},

async removeProfilePhoto() {
return wpAjaxInstance.post(endpoints.REMOVE_PROFILE_PHOTO).then((res) => res.data);
async handleUploadCoverPhoto(files: File[]) {
if (files.length === 0) {
return;
}
const data = {
photo_file: files[0],
photo_type: 'cover_photo',
} satisfies UserPhotoFormProps;
await this.uploadCoverPhotoMutation?.mutate(data);
},

async removeUserPhoto(payload: RemoveUserPhotoProps) {
return wpAjaxInstance.post(endpoints.REMOVE_PROFILE_PHOTO, payload).then((res) => res.data);
},

async handleRemoveProfilePhoto() {
await this.removeProfilePhotoMutation?.mutate({});
await this.removeProfilePhotoMutation?.mutate({ photo_type: 'profile_photo' });
},

async handleRemoveCoverPhoto() {
await this.removeCoverPhotoMutation?.mutate({ photo_type: 'cover_photo' });
},

async updateProfile(payload: AccountFormProps) {
Expand Down
1 change: 1 addition & 0 deletions assets/src/js/v3/shared/icons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const icons = [
'calendarLine',
'calendarLines',
'callEnd',
'camera',
'cart',
'categories',
'certificate',
Expand Down
Loading
Loading