Skip to content

Commit

Permalink
[ADD] Add onboarding for new user explaining how the instance works (#26
Browse files Browse the repository at this point in the history
)

* Create base component

* Add setting logic

* Add step0

* Add steps

* Try fix translation step

* add step3

* Add last screen

* Allow to go backward during onboarding

* onboarding desktop ok

* onboarding mobile ok

Co-authored-by: clovis <[email protected]>
  • Loading branch information
Cl0v1s and clovis authored Aug 21, 2022
1 parent 8d9a886 commit 0aa01d7
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 2 deletions.
21 changes: 20 additions & 1 deletion app/soapbox/__fixtures__/intlMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"preferences.fields.auto_play_gif_label": "Auto-play animated GIFs",
"preferences.fields.enlisted": "Ignore onboarding",
"preferences.fields.boost_modal_label": "Show confirmation dialog before reposting",
"preferences.fields.delete_modal_label": "Show confirmation dialog before deleting a post",
"preferences.fields.demetricator_label": "Use Demetricator",
Expand Down Expand Up @@ -955,5 +956,23 @@
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound",
"who_to_follow.title": "Who To Follow"
"who_to_follow.title": "Who To Follow",
"enlistment.next": "Next",
"enlistment.pass": "Ignore",
"enlistment.step0.title": "Bienvenue sur Mangane !",
"enlistment.step0.body": "Mangane c’est votre porte d’entrée vers un réseau de serveurs indépendants qui communiquent ensemble pour former un réseau social plus large : le fediverse. Chaque serveur est appelé “instance”. Ton instance c’est tout simplement ce site : Bdx.town",
"enlistment.step0.username": "ton pseudo complet",
"enlistment.step0.explanation": "c’est cet identifiant que tu peux partager sur le fediverse",
"enlistment.step1.title": "Comment ça marche ?",
"enlistment.step1.left": "Ici tu es sur {title}. Si tu échanges avec des gens de la même instance que toi, tu peux les mentionners avec simplement <span class='font-bold'>@pseudo</span><br/><br />ex: <a href='{contact_url}'>{contact_name}</a>, si tu veux parler à l’admin de {title}",
"enlistment.step1.right": "Si tu échanges avec une personne d’une autre instance il faudra la mentionner avec son <span class='font-bold'>@pseudo@instance</span><br/><br/> ex: <a href='https://oslo.town/@matt'>@[email protected]</a>, si tu veux parler à l’admin d’Oslo.town",
"enlistment.step1.explanation": "Pas d’inquiétude cependant, lors de la rédaction d’un post, l’autosuggestion t’aidera à trouver la bonne mention ! Par ailleurs si tu réponds à un post, la mention sera automatiquement écrite de la bonne manière.",
"enlistment.step2.title1": "Home",
"enlistment.step2.title3": "Découvrir",
"enlistment.step2.col1": "Ici tu es en terrain connu : seules tes publications et celles des personnes que tu suis s’afficheront sur ce fil.",
"enlistment.step2.col2": "Ici c’est un peu ton quartier : tu n’y trouveras que des publications venant de membres de cette instance, que tu les suives ou non.",
"enlistment.step2.col3": "Sors des sentiers battus et va explorer le reste du monde : ce fil affiche les publications de l’ensemble des instances connues.",
"enlistment.step2.explanation1": "Au départ ce sera un peu vide mais pas de souci on peut t’aider à le fournir !",
"enlistment.step2.explanation2": "On l’appelle généralementfil “local”.",
"enlistment.step2.explanation3": "On l’appelle généralementfil “global” ou “fédéré”."
}
1 change: 1 addition & 0 deletions app/soapbox/actions/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const messages = defineMessages({
});

const defaultSettings = ImmutableMap({
enlisted: false,
onboarded: false,
skinTone: 1,
reduceMotion: false,
Expand Down
100 changes: 100 additions & 0 deletions app/soapbox/features/enlistment/enlistment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useEffect, useState, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { getSettings, changeSettingImmediate } from 'soapbox/actions/settings';
import { useAppSelector } from 'soapbox/hooks';
import classNames from 'classnames';
import { defineMessages, useIntl } from 'react-intl';


import Icon from './../../components/icon';

import Step0 from './steps/step0';
import Step1 from './steps/step1';
import Step2 from './steps/step2';
import Step3 from './steps/step3';
import Step4 from './steps/step4';

const messages = defineMessages({
next: { id: 'enlistment.next', defaultMessage: 'Next' },
pass: { id: 'enlistment.pass', defaultMessage: 'Ignore' },
});

const Steps: Array<React.FC> = [
Step0,
Step1,
Step2,
Step3,
Step4,
];

const Enlistment: React.FC = () => {
const intl = useIntl();
const dispatch = useDispatch();
const [step, setStep] = useState(0);

const StepComponent: React.FC = Steps[step];

const done = useAppSelector(state => getSettings(state).get('enlisted') as boolean);

useEffect(() => {
setStep(0);
}, [done]);

const onPass = useCallback(() => {
dispatch(changeSettingImmediate(['enlisted'], true));
}, []);

const onStep = useCallback((nextStep) => {
if(nextStep >= Steps.length) {
dispatch(changeSettingImmediate(['enlisted'], true));
return;
}
setStep(nextStep);
}, [setStep, dispatch]);

const onNext = useCallback(() => {
onStep(step + 1);
}, [onStep, step]);

if(done) return null;

return (
<div className="component-enlistment">
<section className="bg-white dark:bg-slate-800 drop-shadow-2xl">
<StepComponent />
<div className='enlistment__bar flex justify-between items-center p-4'>
<a onClick={onPass} className="text-gray-200 hover:underline opacity-50 cursor-pointer">
{intl.formatMessage(messages.pass)}
</a>
<div className='circles flex items-center'>
{
Steps.map((e, index) => (
<div
className={classNames({
"cursor-pointer mx-2 border-2 border-solid border-white": true,
"bg-white": index === step
})}
onClick={() => onStep(index)}
>
</div>
))
}
</div>
<button
type="button"
className="flex items-center text-gray-200 hover:text-white"
onClick={onNext}
>
{intl.formatMessage(messages.next)}
<Icon
className="ml-1"
src={require('@tabler/icons/arrow-right.svg')}
/>
</button>
</div>
</section>
</div>
)
};

export default Enlistment;
44 changes: 44 additions & 0 deletions app/soapbox/features/enlistment/steps/step0.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useOwnAccount, useAppSelector } from 'soapbox/hooks';

const messages = defineMessages({
title: { id: 'enlistment.step0.title'},
body: { id: 'enlistment.step0.body'},
username: { id: 'enlistment.step0.username'},
explanation: { id: 'enlistment.step0.explanation'},
});

const Step0: React.FC = () => {
const intl = useIntl();
const account = useOwnAccount();
const instance = useAppSelector((state: any) => state.instance);

return (
<div className="enlistment__step0 mx-auto py-10 px-5">
<h3 className="text-2xl font-bold">
{intl.formatMessage(messages.title)}
</h3>
<p className="mb-5">
<span dangerouslySetInnerHTML={{ __html: intl.formatMessage(messages.body)}} />
&nbsp;
<span className="font-bold">
{instance.get("uri").replace(/https?:\/\//, '')}
</span>
</p>

<h4 className="uppercase text-lg mb-2">
{intl.formatMessage(messages.username)}
</h4>

<div className="enlisted__step0__username inline-block rounded p-1 text-gray-200 text-lg font-bold">
@{account?.acct}@{instance.get("uri").replace(/https?:\/\//, '')}
</div>
<div className="italic mt-2">
{intl.formatMessage(messages.explanation)}
</div>
</div>
)
};

export default Step0;
53 changes: 53 additions & 0 deletions app/soapbox/features/enlistment/steps/step1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useMemo } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useAppSelector } from 'soapbox/hooks';

const messages = defineMessages({
right: { id: 'enlistment.step1.right'},
});

const Step1: React.FC = () => {
const intl = useIntl();

const instance = useAppSelector((state) => state.instance);

const contactName = useMemo(() => `@${instance.get("email").replace(/@.+/, '')}`, [instance]);
const contactUrl = useMemo(() => `${instance.get("uri")}/${contactName}`, [contactName]);

return (
<div className="enlistment__step1 mx-auto py-10 px-5">
<h3 className="text-2xl font-bold">
<FormattedMessage id="enlistment.step1.title" />
</h3>
<div className="flex mt-2">
<div className="pr-6 w-1/2">
<p>
<FormattedMessage
id="enlistment.step1.left"
values={{
title: instance.get("title"),
username: (
<span className="font-bold"><FormattedMessage id="enlistment.step1.username" /></span>
),
contact: (
<a href={contactUrl} target="_blank">
{contactName}
</a>
),
br: <br />
}}
/>
</p>
</div>
<div className="pl-6 w-1/2">
<p dangerouslySetInnerHTML={{ __html: intl.formatMessage(messages.right)}} />
</div>
</div>
<div className="italic mt-8">
<FormattedMessage id="enlistment.step1.explanation" />
</div>
</div>
)
};

export default Step1;
51 changes: 51 additions & 0 deletions app/soapbox/features/enlistment/steps/step2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useAppSelector } from 'soapbox/hooks';

const messages = defineMessages({
col1: { id: 'enlistment.step2.col1'},
col2: { id: 'enlistment.step2.col2'},
col3: { id: 'enlistment.step2.col3'},
});

const Step2: React.FC = () => {
const intl = useIntl();

const instance = useAppSelector((state) => state.instance);

return (
<div className="enlistment__step2 mx-auto py-10 px-5">
<div className="flex mt-3">
<div className="w-1/3">
<h3 className="text-xl font-bold">
<FormattedMessage id="enlistment.step2.title1" />
</h3>
<p dangerouslySetInnerHTML={{ __html: intl.formatMessage(messages.col1)}} />
<p className="mt-4 italic">
<FormattedMessage id="enlistment.step2.explanation1" />
</p>
</div>
<div className="mx-6 w-1/3">
<h3 className="text-xl font-bold">
{instance.get("title")}
</h3>
<p dangerouslySetInnerHTML={{ __html: intl.formatMessage(messages.col2)}} />
<p className="mt-4 italic">
<FormattedMessage id="enlistment.step2.explanation2" />
</p>
</div>
<div className="w-1/3">
<h3 className="text-xl font-bold">
<FormattedMessage id="enlistment.step2.title3" />
</h3>
<p dangerouslySetInnerHTML={{ __html: intl.formatMessage(messages.col3)}} />
<p className="mt-4 italic">
<FormattedMessage id="enlistment.step2.explanation3" />
</p>
</div>
</div>
</div>
)
};

export default Step2;
63 changes: 63 additions & 0 deletions app/soapbox/features/enlistment/steps/step3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import { useIntl, FormattedMessage } from 'react-intl';

import Icon from './../../../components/icon';

const Step3: React.FC = () => {
return (
<div className="enlistment__step3 mx-auto py-10 px-5">
<div>
<h3 className="text-2xl font-bold">
<FormattedMessage id="enlistment.step3.title" />
</h3>
<p>
<FormattedMessage id="enlistment.step3.description" />
</p>
</div>
<div className="mt-10">
<div className='flex'>
<div className='w-1/2 pr-2'>
<h4 className="flex items-center text-xl font-bold">
<Icon className="mr-1 w-6 h-6" src={require("@tabler/icons/world.svg")} />
<FormattedMessage id="enlistment.step3.public-title" />
</h4>
<p>
<FormattedMessage id="enlistment.step3.public-description" />
</p>
</div>
<div className="w-1/2 pr-2">
<h4 className='flex items-center text-xl font-bold'>
<Icon className='mr-1 w-6 h-6' src={require("@tabler/icons/lock-open.svg")} />
<FormattedMessage id="enlistment.step3.unlisted-title" />
</h4>
<p>
<FormattedMessage id="enlistment.step3.unlisted-description" />
</p>
</div>
</div>
<div className='mt-10 flex'>
<div className='w-1/2 pr-2'>
<h4 className='flex items-center text-xl font-bold'>
<Icon className='mr-1 w-6 h-6' src={require("@tabler/icons/lock.svg")} />
<FormattedMessage id="enlistment.step3.followers-title" />
</h4>
<p>
<FormattedMessage id="enlistment.step3.followers-description" />
</p>
</div>
<div className='w-1/2 pl-2'>
<h4 className='flex items-center text-xl font-bold'>
<Icon className='mr-1 w-6 h-6' src={require("@tabler/icons/mail.svg")} />
<FormattedMessage id="enlistment.step3.direct-title" />
</h4>
<p>
<FormattedMessage id="enlistment.step3.direct-description" />
</p>
</div>
</div>
</div>
</div>
)
};

export default Step3;
Loading

0 comments on commit 0aa01d7

Please sign in to comment.