@@ -420,7 +420,7 @@ module.exports = function(migration) {
},

message:
"Keep it short and sweet! This should mirror the description from our partner's site.",
"The scholarship description is limited to 300 characters! This should mirror the description from our partner's site.",
},
{
nodes: {},
@@ -52,7 +52,7 @@ const CampaignInfoBlock = ({
<h1 className="mb-3 text-lg uppercase">Campaign Info</h1>
) : null}
<dl className="clearfix">
{scholarshipAmount && isOpen ? (
{scholarshipAmount && scholarshipDeadline && isOpen ? (
<React.Fragment>
<dt className="campaign-info__scholarship">
Win A Scholarship
@@ -6,6 +6,7 @@ import BadgesTab from './BadgesTab';
import Profile from './Profile';
import Subscriptions from './Subscriptions';
import UserPostsQuery from './UserPostsQuery';
import DeleteAccountTab from './DeleteAccountTab';

const AccountRoute = props => (
<Switch>
@@ -20,6 +21,10 @@ const AccountRoute = props => (
render={() => <Subscriptions {...props} />}
/>
<Route path="/us/account/profile" render={() => <Profile {...props} />} />
<Route
path="/us/account/delete"
render={() => <DeleteAccountTab {...props} />}
/>
<Route
path="/us/account/campaigns"
render={() => <UserPostsQuery userId={props.userId} />}
@@ -0,0 +1,140 @@
import React from 'react';
import gql from 'graphql-tag';
import classnames from 'classnames';
import { format, formatDistanceStrict, addDays } from 'date-fns';
import { useQuery, useMutation } from '@apollo/react-hooks';

const DeleteAccountQuery = gql`
query DeleteAccountQuery($id: String!) {
user(id: $id) {
id
deletionRequestedAt
}
}
`;

// We're including 'emailSubscriptionTopics' in this query so that
// if the user clicks over to their subscriptions tab, this update
// will be displayed (without a full page refresh).
const DeleteAccountMutation = gql`
mutation DeleteAccountMutation($id: String!) {
requestDeletion(id: $id) {
id
deletionRequestedAt
emailSubscriptionTopics
}
}
`;

const UndoAccountDeletionMutation = gql`
mutation UndoAccountDeletionMutation($id: String!) {
undoDeletionRequest(id: $id) {
id
deletionRequestedAt
}
}
`;

const DeleteAccountForm = () => {
const options = { variables: { id: window.AUTH.id } };

const { data, loading, error } = useQuery(DeleteAccountQuery, options);

const [undo] = useMutation(UndoAccountDeletionMutation, options);
const [deleteUser, { loading: modifying }] = useMutation(
DeleteAccountMutation,
options,
);

if (error) {
return <p>Something went wrong!</p>;
}

if (loading) {
return <div className="spinner" />;
}

const { deletionRequestedAt } = data.user;

// If we don't have a pending deletion request, let user submit one:
if (!deletionRequestedAt) {
return (
<button
onClick={deleteUser}
type="button"
className={classnames('button bg-red-500 hover:bg-red-300', {
'is-loading': modifying,
})}
>
Delete My Account
</button>
);
}

const scheduledDeletion = addDays(deletionRequestedAt, 14);
const remainingDays = formatDistanceStrict(new Date(), scheduledDeletion, {
unit: 'day',
roundingMethod: 'ceil',
});

// Otherwise, display some information about their request:
return (
<>
<p>
<strong className="text-red-500">
We received your account deletion request on{' '}
{format(deletionRequestedAt, 'PPP')}.
</strong>{' '}
You have been unsubscribed from all DoSomething.org emails &amp; text
marketing, and your account will be permanently deleted within{' '}
{remainingDays}.
</p>
<p>
We&apos;ll send you one final reminder message before that happens. In
the meantime, you can always change your mind:
</p>
<p>
<button type="button" className="link-button" onClick={undo}>
I changed my mind! Don&apos;t delete my account.
</button>
</p>
</>
);
};

const DeleteAccountTab = () => {
return (
<>
<div className="grid-wide-2/3 pb-6">
<h2>Delete My Account</h2>
<p>We&apos;re sorry to see you go!</p>

<p>
Requesting to delete your account will immediately unsubscribe you
from receiving email &amp; text marketing. After 14 days, we will also
delete all of your data from our systems. This includes:
</p>
<ul className="list">
<li>
Profile information (such as name, preferences, &amp; contact
methods)
</li>
<li>
Campaign submissions, including photos &amp; scholarship entries
</li>
<li>Email and SMS messaging history</li>
</ul>

<p>
After this process is complete, it will be{' '}
<strong>impossible to recover</strong> your account.
</p>
</div>
<div className="grid-wide-2/3 pb-6">
<DeleteAccountForm />
</div>
</>
);
};

export default DeleteAccountTab;
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

import FormItem from './FormItem';
import { env } from '../../../helpers/index';
@@ -46,12 +47,12 @@ const Profile = props => (
<h3>Data and Privacy</h3>
<ul className="mt-3">
<li>
<a
href="mailto:trust@dosomething.org?subject=Delete my account"
<Link
to="/us/account/delete"
className="text-gray-600 font-normal underline"
>
Delete my account
</a>
</Link>
</li>
</ul>

@@ -65,10 +65,10 @@ const CtaPopoverEmailForm = ({ handleComplete }) => {
console.log('🚫 failed response? caught the error!', error);
}

trackAnalyticsEvent('failed_call_to_action_popover_submission', {
trackAnalyticsEvent('failed_call_to_action_popover', {
action: 'form_failed',
category: EVENT_CATEGORIES.siteAction,
label: 'call_to_action_popover_submission',
label: 'call_to_action_popover',
context: {
contextSource: 'newsletter_scholarships',
error,
@@ -39,6 +39,16 @@ $forge-path: '../../../node_modules/@dosomething/forge/assets/';
box-shadow: 0 0 3px theme('colors.blue.500');
}

// Helper class to style 'button' elements as links:
.link-button {
@apply inline bg-transparent border-none cursor-pointer text-blue-500 underline m-0 p-0;
}

.link-button:hover,
.link-button:focus {
@apply text-blue-300;
}

@tailwind utilities;

/*