Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/perfect-zebras-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/localizations': minor
'@clerk/clerk-js': minor
'@clerk/types': minor
---

Adds the ability for users to delete their own accounts, as long as they have permission to do so
86 changes: 43 additions & 43 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"start": "echo \"Noop\"",
"test": "jest",
"test:ci": "jest --maxWorkers=50%",
"test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html"
"test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html",
"watch": "webpack --config webpack.config.js --env production --watch"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this command to pair with the playground file I added here while developing the feature. When running this in the terminal, it will rebuild changed files more quickly than running a full build after every save.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me. Just curious, does HMR work correctly when using playground.html? I think HMR won't work unless the file is served under https as HMR relies on webhooks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not, requires a manual reload. Maybe we can tinker with this to make it smoother down the line!

},
"dependencies": {
"@clerk/localizations": "^1.18.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/src/core/resources/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ export class User extends BaseResource implements UserResource {
});
};

delete = (): Promise<void> => {
return this._baseDelete({ path: '/me' });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to flag this, as it's a somewhat non-standard path. Once we fully roll this out in the API there's no going back without a major version boost.

};

getSessions = async (): Promise<SessionWithActivities[]> => {
if (this.cachedSessionsWithActivities) {
return this.cachedSessionsWithActivities;
Expand Down
3 changes: 3 additions & 0 deletions packages/clerk-js/src/core/resources/UserSettings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Attributes,
Actions,
OAuthProviders,
OAuthStrategy,
PasswordSettingsData,
Expand Down Expand Up @@ -29,6 +30,7 @@ export class UserSettings extends BaseResource implements UserSettingsResource {
saml!: SamlSettings;

attributes!: Attributes;
actions!: Actions;
signIn!: SignInData;
signUp!: SignUpData;
passwordSettings!: PasswordSettingsData;
Expand Down Expand Up @@ -65,6 +67,7 @@ export class UserSettings extends BaseResource implements UserSettingsResource {
this.attributes = Object.fromEntries(
Object.entries(data.attributes).map(a => [a[0], { ...a[1], name: a[0] }]),
) as Attributes;
this.actions = data.actions;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This small modification allows the environment context to have access to this new "actions" property which contains the values for permissions associated with user actions, which are needed for the user delete feature. Big thanks for @SokratisVidros for helping me to find this and patch this value into the right place.

this.signIn = data.sign_in;
this.signUp = data.sign_up;
this.passwordSettings = {
Expand Down
42 changes: 42 additions & 0 deletions packages/clerk-js/src/ui/components/UserProfile/DeletePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useEnvironment, useCoreUser, useCoreClerk } from '../../contexts';
import { localizationKeys, Text } from '../../customizables';
import { ContentPage, Form, FormButtons, useCardState, withCardStateProvider } from '../../elements';
import { handleError } from '../../utils';
import { UserProfileBreadcrumbs } from './UserProfileNavbar';
import { useRouter } from '../../router';

export const DeletePage = withCardStateProvider(() => {
const card = useCardState();
const environment = useEnvironment();
const router = useRouter();
const user = useCoreUser();
const clerk = useCoreClerk();

const deleteUser = async () => {
try {
await user.delete();
if (clerk.client.activeSessions.length > 0) {
await router.navigate(environment.displayConfig.afterSignOutOneUrl);
} else {
await router.navigate(environment.displayConfig.afterSignOutAllUrl);
}
} catch (e) {
handleError(e, [], card.setError);
}
};

return (
<ContentPage
headerTitle={localizationKeys('userProfile.deletePage.title')}
Breadcrumbs={UserProfileBreadcrumbs}
>
<Form.Root onSubmit={deleteUser}>
<Text localizationKey={localizationKeys('userProfile.deletePage.description')} />
<FormButtons
submitLabel={localizationKeys('userProfile.deletePage.confirm')}
colorScheme='danger'
/>
</Form.Root>
</ContentPage>
);
});
38 changes: 38 additions & 0 deletions packages/clerk-js/src/ui/components/UserProfile/DeleteSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { localizationKeys, Flex, Text, Col, Button } from '../../customizables';
import { ProfileSection } from '../../elements';
import { useRouter } from '../../router';

export const DeleteSection = () => {
const { navigate } = useRouter();

return (
<ProfileSection
title={localizationKeys('userProfile.start.dangerSection.title')}
id='danger'
>
<Flex
justify='between'
sx={t => ({ marginTop: t.space.$2, marginLeft: t.space.$6 })}
>
<Col gap={1}>
<Text
variant='regularMedium'
localizationKey={localizationKeys('userProfile.start.dangerSection.deleteAccountTitle')}
/>
<Text
variant='smallRegular'
colorScheme='neutral'
localizationKey={localizationKeys('userProfile.start.dangerSection.deleteAccountDescription')}
/>
</Col>
<Button
aria-label='Delete account'
colorScheme='danger'
textVariant='buttonExtraSmallBold'
onClick={() => navigate(`delete`)}
localizationKey={localizationKeys('userProfile.start.dangerSection.deleteAccountButton')}
/>
</Flex>
</ProfileSection>
);
};
5 changes: 4 additions & 1 deletion packages/clerk-js/src/ui/components/UserProfile/RootPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { UsernameSection } from './UsernameSection';
import { UserProfileSection } from './UserProfileSection';
import { getSecondFactors } from './utils';
import { Web3Section } from './Web3Section';
import { DeleteSection } from './DeleteSection';

export const RootPage = withCardStateProvider(() => {
const { attributes, saml, social, instanceIsPasswordBased } = useEnvironment().userSettings;
const { attributes, saml, social, actions, instanceIsPasswordBased } = useEnvironment().userSettings;
const card = useCardState();
const user = useCoreUser();
const showUsername = attributes.username.enabled;
Expand All @@ -26,6 +27,7 @@ export const RootPage = withCardStateProvider(() => {
const showWeb3 = attributes.web3_wallet.enabled;
const showPassword = instanceIsPasswordBased;
const showMfa = getSecondFactors(attributes).length > 0;
const showDelete = actions.delete_self;

return (
<Col
Expand Down Expand Up @@ -54,6 +56,7 @@ export const RootPage = withCardStateProvider(() => {
{showConnectedAccounts && <ConnectedAccountsSection />}
{showSamlAccounts && <EnterpriseAccountsSection />}
{showWeb3 && <Web3Section />}
{showDelete && <DeleteSection />}
</Col>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { RootPage } from './RootPage';
import { UsernamePage } from './UsernamePage';
import { Web3Page } from './Web3Page';
import { DeletePage } from './DeletePage';

export const UserProfileRoutes = (props: PropsOfComponent<typeof ProfileCardContent>) => {
return (
Expand Down Expand Up @@ -102,6 +103,9 @@ export const UserProfileRoutes = (props: PropsOfComponent<typeof ProfileCardCont
<PasswordPage />
</Route>
{/*</Route>*/}
<Route path='delete'>
<DeletePage />
</Route>
</ProfileCardContent>
);
};
Loading