Skip to content
Draft
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
143 changes: 78 additions & 65 deletions js/app/packages/app/component/settings/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { uploadProfilePicture } from '@core/component/ProfilePicture';
import { TabContentRow } from '@core/component/TabContent';
import EditableField from '@core/component/EditableField';
import { capitalize } from '@block-pdf/util/StringUtils';
import { DeprecatedTextButton } from '@core/component/DeprecatedTextButton';
import { useHasPaidAccess } from '@core/auth/license';
import { UserIcon } from '@core/component/UserIcon';
import { useLogout } from '@core/auth/logout';
import {
blockNameToFileExtensions,
Expand All @@ -23,7 +20,6 @@ import {
} from '@core/signal/profilePicture';
import { useOrganizationName } from '@core/user';
import Logout from '@icon/regular/sign-out.svg';
import { Popover } from '@kobalte/core';
import IconUpload from '@macro-icons/macro-upload.svg';
import { authServiceClient } from '@service-auth/client';
import { useEmail, useLicenseStatus, useUserId } from '@service-gql/client';
Expand All @@ -32,15 +28,40 @@ import {
useEmailLinks,
useEmailLinksStatus,
} from '@core/email-link';
import { BetaTooltip } from '../BetaTooltip';
import {
type SupportedNotificationSettings,
useNotificationSettings,
} from '@notifications';
import { toast } from '@core/component/Toast/Toast';
import { staticFileIdEndpoint } from '@core/constant/servers';
import { createStaticFile } from '@core/util/create';
import { Button } from '@ui/components/Button';
import { BetaBadge } from '@core/component/BetaBadge';
import { Avatar } from '@ui/components/Avatar';

// NOTE: solid directives
false && fileSelector;

// 16 megabytes
const MAX_FILE_SIZE = 16 * 1000 * 1000;

async function uploadProfilePicture(
file: File
): Promise<{ id: string; url: string } | void> {
if (file.size > MAX_FILE_SIZE) {
return toast.failure('Image size too large');
}

try {
const id = await createStaticFile(file);
const url = staticFileIdEndpoint(id);
await authServiceClient.putProfilePicture({ url });
return { id, url };
} catch (_error) {
return toast.failure('Failed to upload profile picture');
}
}

function useUserName() {
const fetchUserName = async () => {
const [_, response] = await authServiceClient.getUserName();
Expand Down Expand Up @@ -83,7 +104,7 @@ export function Account() {
>(undefined);

const emailActive = useEmailLinksStatus();
const [showTooltip, setShowTooltip] = createSignal<boolean>(false);
const [profilePicUrl] = useProfilePictureUrl(userId());

const firstName = () => {
// Display any updated first name immediately without having to refetch
Expand All @@ -105,6 +126,10 @@ export function Account() {
return undefined;
};

const nameOrEmail = () => {
return [firstName(), lastName()].filter(Boolean).join(' ') || email() || 'User';
};

const logoutHandler = () => {
let redirectUrl = window.location.origin;
logout(redirectUrl);
Expand All @@ -122,7 +147,12 @@ export function Account() {
>
<Show when={userId()}>
<div class="flex items-center">
<UserIcon id={userId() as string} isDeleted={false} size="lg" />
<Avatar
for={nameOrEmail()}
src={profilePicUrl()}
class="text-lg leading-loose"
/>

<div
class="ml-2"
use:fileSelector={{
Expand All @@ -144,7 +174,9 @@ export function Account() {
},
}}
>
<DeprecatedTextButton text="Upload" icon={IconUpload} theme="accent" />
<Button class="bg-accent/10 hover:bg-accent/20 text-accent-ink border-accent/30 text-xs p-2">
<IconUpload class="h-[1lh]" /> Upload
</Button>
</div>
</div>
</Show>
Expand Down Expand Up @@ -188,59 +220,46 @@ export function Account() {
subtext={capitalize(licenseStatus() ?? '')}
/>
<Show when={!hasPaidAccess()}>
<DeprecatedTextButton
theme="accent"
text="Upgrade"
<Button
variant="primary"
onClick={() => showPaywall()}
class="mb-[18px]"
/>
>
Upgrade
</Button>
</Show>
</div>
<Show when={ENABLE_EMAIL && (!emailActive() || DEV_MODE_ENV)}>
<div
class={`flex items-center justify-between ${!showEmailModal() && 'mb-[18px]'}`}
class={`flex items-center text-sm justify-between ${!showEmailModal() && 'mb-[18px]'}`}
>
<div class="text-sm">Email</div>
<Show
when={!emailActive() && DEV_MODE_ENV}
fallback={
<DeprecatedTextButton
theme="base"
text="Disable"
<Button
variant="secondary"
onClick={() => {
setShowEmailModal(true);
}}
/>
setShowEmailModal(true);
}}>
Disable
</Button>
}
>
<Popover.Root open={showTooltip()} gutter={10} placement={'left'}>
<Popover.Anchor>
<div
class="flex flex-col items-center"
onPointerEnter={() => {
setShowTooltip(true);
}}
onPointerLeave={() => {
setShowTooltip(false);
}}
>
<DeprecatedTextButton
theme="base"
text="Enable"
onClick={connectEmail}
/>
</div>
</Popover.Anchor>
<Popover.Portal>
<Popover.Content class="z-modal">
<BetaTooltip
text={
"Enabling an email address different from the current Macro user's will result in session termination"
}
/>
</Popover.Content>
</Popover.Portal>
</Popover.Root>

<Button
variant="secondary"
onClick={connectEmail}
tooltip={
<div>
<div class="w-min bg-surface-0 rounded-lg mb-2">
<BetaBadge />
</div>
<p class="max-w-40">Enabling an email address different from the current Macro user's will result in session termination.</p>
</div>}
>
Enable
</Button>
</Show>
</div>
</Show>
Expand All @@ -249,22 +268,15 @@ export function Account() {
<div class="mb-[18px] text-sm pt-4">
Disabling will clear all email data from Macro
</div>
<div class="ml-auto flex flex-row">
<DeprecatedTextButton
theme="clear"
text="Confirm"
<div class="ml-auto flex flex-row text-xs gap-1">
<Button
variant="destructive"
onClick={() => {
disconnectEmail();
setShowEmailModal(false);
}}
/>
<DeprecatedTextButton
theme="clear"
text="Cancel"
onClick={() => {
setShowEmailModal(false);
}}
/>
}}>Confirm</Button>

<Button onClick={() => setShowEmailModal(false)}>Cancel</Button>
</div>
</div>
</Show>
Expand Down Expand Up @@ -301,13 +313,14 @@ function NotificationSettings(props: {
settings: SupportedNotificationSettings;
}) {
return (
<div class="flex items-center justify-between mb-[18px]">
<div class="flex items-center justify-between mb-[18px] text-sm">
<div class="text-sm">Notifications</div>
<DeprecatedTextButton
theme="base"
text={props.settings.isEnabled() ? "Disable" : "Enable"}
<Button
variant="secondary"
onClick={() => props.settings.toggle(!props.settings.isEnabled())}
/>
>
{props.settings.isEnabled() ? "Disable" : "Enable"}
</Button>
</div>
);
}
Expand Down
30 changes: 0 additions & 30 deletions js/app/packages/core/component/ProfilePicture.stories.tsx

This file was deleted.

30 changes: 6 additions & 24 deletions js/app/packages/core/component/ProfilePicture.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { toast } from '@core/component/Toast/Toast';
import { ENABLE_PROFILE_PICTURES } from '@core/constant/featureFlags';
import { staticFileIdEndpoint } from '@core/constant/servers';

import { useProfilePictureUrl } from '@core/signal/profilePicture';
import { idToEmail } from '@core/user';
import { createStaticFile } from '@core/util/create';
import { authServiceClient } from '@service-auth/client';
import { createMemo, Show } from 'solid-js';
import type { SizeClass } from './UserIcon';

Expand All @@ -17,26 +14,11 @@ type ProfilePictureProps = {
fetchUrl?: boolean;
};

// 16 megabytes
const MAX_FILE_SIZE = 16 * 1000 * 1000;

export async function uploadProfilePicture(
file: File
): Promise<{ id: string; url: string } | void> {
if (file.size > MAX_FILE_SIZE) {
return toast.failure('Image size too large');
}

try {
const id = await createStaticFile(file);
const url = staticFileIdEndpoint(id);
await authServiceClient.putProfilePicture({ url });
return { id, url };
} catch (_error) {
return toast.failure('Failed to upload profile picture');
}
}

/**
* ProfilePicture renders a profile picture for a user.
*
* @deprecated Use the Avatar component instead.
*/
export function ProfilePicture(props: ProfilePictureProps) {
const email = createMemo(() => {
const id = props.id;
Expand Down
Loading
Loading