راستی آزمایی ایمیل (Email Confirmation) با React — راهنمای کاربردی
در این نوشته به بررسی روش تأیید ایمیل یا همان راستی آزمایی ایمیل در React میپردازیم.
روش تأیید ایمیل
شاید تأیید ایمیل و راستی آزمایی آن موضوع سادهای به نظر برسد. تأیید ایمیل دستکم از زمانی که خود ایمیل متولد شده، وجود داشته است و ما گردش کاری آن را به خوبی میدانیم. یک ایمیل را برای تأیید از کاربر دریافت میکنیم و یک لینک به آن آدرس ارسال میکنیم. سپس پایگاه داده را بهروزرسانی میکنیم تا زمانی که کاربر روی لینک کلیک کرد، ایمیل وی را تأیید کند. همان طور که میبینید همه کار در سه گام ساده اجرا میشود. اما برای این که بتوان این کار را انجام داد به یک اپلیکیشن کامل نیاز داریم و از این رو فرصت مناسبی برای یادگیری طرز کار Mongo ،Express ،React و Node و همکاری آنها با هم به نظر میآید.
کلاینت React
کلاینت خود را به وسیله Create React App (+) بوتاسترپ میکنیم. ما فرض میکنیم که شما از قبل تجربیاتی در این زمینه دارید.
برای این که بتوانید مطالب مطرح شده در این راهنما را دنبال کنید تنها به سه چیز نیاز دارید:
- یک اپلیکیشن که پیش از آن که بتواند کاری انجام دهد در وضعیت قابل کارکرد باشد.
- بازخورد یعنی چیزی که وقتی اپلیکیشن کاری انجام میدهد اتفاق میافتد.
- تأییدیه که نشان میدهد اپلیکیشن کار خود را به پایان برده است.
کلاینت ما برای این اپلیکیشن تنها چند کامپوننت دارد و میتواند با کد و کامنت بسیار کمی که در ادامه نشان میدهیم نوشته شود. در این آموزش تلاش نکردهایم از حجم کد بکاهیم تا تجربه کاربری بهتری داشته باشیم.
نکته: شما میتوانید از Bit (+) برای مدیریت کامپوننتهای با قابلیت استفاده مجدد بهره بگیرید و در زمان خود صرفهجویی کنید. مجموعهای از کامپوننتهای مفید را نگهداری کنید و از آنها در پروژههای مختلف استفاده کرده و به سادگی تغییرات را همگامسازی کنید. Bit به ساخت سریعتر اپلیکیشنها کمک زیادی میکند.
فایل App.js
اینک نوبت به کدنویسی در زبان جاوا اسکریپت رسیده است. فایل App.js اپلیکیشن ما به صورت زیر خواهد بود:
1import React, { Component } from 'react'
2import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'
3import Notifications from 'react-notify-toast'
4import 'react-toastify/dist/ReactToastify.css'
5
6import Landing from './components/Landing'
7import Confirm from './components/Confirm'
8import Spinner from './components/Spinner'
9import Footer from './components/Footer/Footer'
10import { API_URL } from './config'
11import './App.css'
12
13export default class App extends Component {
14
15 // A bit of state to make sure the server is up and running before the user
16 // can interact with the app.
17 state = {
18 loading: true
19 }
20
21 // When the component mounts, a simple GET request is made to 'wake up' the
22 // server. A lot of free services like Heroku and Now.sh will put your server
23 // to sleep if no one has used your application in a few minutes. Using a
24 // service like uptimerobot.com to ping the server regularly can mitigate
25 // sleepiness.
26 componentDidMount = () => {
27 fetch(`${API_URL}/wake-up`)
28 .then(res => res.json())
29 .then(() => {
30 this.setState({ loading: false })
31 })
32 .catch(err => console.log(err))
33 }
34
35 // You are probaly used to seeing React 'render()' methods written like this:
36 //
37 // render() {
38 // return (
39 // <Some jsx />
40 // )
41 // }
42 //
43 // Below is a version of writing a 'render()' that also works. The 'why does
44 // it work?' is related to the 'this' keyword in JavaScript and is beyond the
45 // scope of this post.
46
47 render = () => {
48
49 // The 'content' function determines what to show the user based on whether
50 // the server is awake or not.
51 const content = () => {
52
53 // The server is still asleep, so provide a visual cue with the <Spinner />
54 // component to give the user that feedback.
55 if (this.state.loading) {
56 return <Spinner size='8x' spinning='spinning' />
57 }
58
59 // The server is awake! React Router is used to either show the
60 // <Landing /> component where the emails are collected or the <Confirm />
61 // component where the emails are confirmed.
62 return (
63 <BrowserRouter>
64 <Switch>
65 {/*
66 the ':id' in this route will be the unique id the database
67 creates and is available on 'this.props' inside the <Confirm />
68 component at this.props.match.params.id
69 */}
70 <Route exact path='/confirm/:id' component={Confirm} />
71 <Route exact path='/' component={Landing} />
72 <Redirect from='*' to='/'/>
73 </Switch>
74 </BrowserRouter>
75 )
76 }
77
78 return (
79 // The 'container' class uses flexbox to position and center its three
80 // children: <Notifications />, <main> and <Footer />
81 <div className='container fadein'>
82 {/*
83 <Notifications > component from 'react-notify-toast' This is the
84 placeholder on the dom that will hold all the feedback toast messages
85 whenever notify.show('My Message!') is called.
86 */}
87 <Notifications />
88 <main>
89 {content()}
90 </main>
91 {/*
92 For every Medium post I write I include a demo app that uses the same
93 footer. So, I have abstracted that out to use on future posts with
94 just a couple of props passed in.
95 */}
96 <Footer
97 mediumId={'257e5d9de725'}
98 githubRepo={'react-confirm-email'}
99 />
100 </div>
101 )
102 }
103}
فایل Landing.js
این فایل شامل کامپوننتی است که وقتی کاربر وارد اپلیکیشن میشود نمایش مییابد.
1import React, { Component } from 'react'
2import { notify } from 'react-notify-toast'
3import Spinner from './Spinner'
4import { API_URL } from '../config'
5
6export default class Landing extends Component {
7
8 // A bit of state to give the user feedback while their email address is being
9 // added to the User model on the server.
10 state = {
11 sendingEmail: false
12 }
13
14 onSubmit = event => {
15 event.preventDefault()
16 this.setState({ sendingEmail: true})
17
18 // Super interesting to me that you can mess with the upper and lower case
19 // of the headers on the fetch call and the world does not explode.
20 fetch(`${API_URL}/email`, {
21 method: 'pOSt',
22 headers: {
23 aCcePt: 'aPpliCaTIon/JsOn',
24 'cOntENt-type': 'applicAtion/JSoN'
25 },
26 body: JSON.stringify({ email: this.email.value })
27 })
28 .then(res => res.json())
29 .then(data => {
30
31 // Everything has come back successfully, time to update the state to
32 // reenable the button and stop the <Spinner>. Also, show a toast with a
33 // message from the server to give the user feedback and reset the form
34 // so the user can start over if she chooses.
35 this.setState({ sendingEmail: false})
36 notify.show(data.msg)
37 this.form.reset()
38 })
39 .catch(err => console.log(err))
40 }
41
42 render = () => {
43
44 // This bit of state provides user feedback in the component when something
45 // changes. sendingEmail is flipped just before the fetch request is sent in
46 // onSubmit and then flipped back when data has been received from the server.
47 // How many times is the 'sendingEmail' variable used below?
48 const { sendingEmail } = this.state
49
50 return (
51 // A ref is put on the form so that it can be reset once the submission
52 // process is complete.
53 <form
54 onSubmit={this.onSubmit}
55 ref={form => this.form = form}
56 >
57 <div>
58 <input
59 type='email'
60 name='email'
61 ref={input => this.email = input}
62 required
63 />
64 {/*
65 Putting the label after the input allows for that neat transition
66 effect on the label when the input is focused.
67 */}
68 <label htmlFor='email'>email</label>
69 </div>
70 <div>
71 {/*
72 While the email is being sent from the server, provide feedback that
73 something is happening by disabling the button and showing a
74 <Spinner /> inside the button with a smaller 'size' prop passed in.
75 */}
76 <button type='submit' className='btn' disabled={sendingEmail}>
77 {sendingEmail
78 ? <Spinner size='lg' spinning='spinning' />
79 : "Let's Go!"
80 }
81 </button>
82 </div>
83 </form>
84 )
85 }
86}
فایل Confirm.js
هنگامی که کاربر روی لینک یکتای موجود در ایمیلی که به آدرسش ارسال کردیم کلیک کند، این کامپوننت از سوی React Router (+) بارگذاری میشود.
1import React, {Component} from 'react'
2import { Link } from 'react-router-dom'
3import { notify } from 'react-notify-toast'
4import Spinner from './Spinner'
5import { API_URL } from '../config'
6
7export default class Confirm extends Component {
8
9 // A bit of state to give the user feedback while their email
10 // address is being confirmed on the User model on the server.
11 state = {
12 confirming: true
13 }
14
15 // When the component mounts the mongo id for the user is pulled from the
16 // params in React Router. This id is then sent to the server to confirm that
17 // the user has clicked on the link in the email. The link in the email will
18 // look something like this:
19 //
20 // http://localhost:3000/confirm/5c40d7607d259400989a9d42
21 //
22 // where 5c40d...a9d42 is the unique id created by Mongo
23 componentDidMount = () => {
24 const { id } = this.props.match.params
25
26 fetch(`${API_URL}/email/confirm/${id}`)
27 .then(res => res.json())
28 .then(data => {
29 this.setState({ confirming: false })
30 notify.show(data.msg)
31 })
32 .catch(err => console.log(err))
33 }
34
35 // While the email address is being confirmed on the server a spinner is
36 // shown that gives visual feedback. Once the email has been confirmed the
37 // spinner is stopped and turned into a button that takes the user back to the
38 // <Landing > component so they can confirm another email address.
39 render = () =>
40 <div className='confirm'>
41 {this.state.confirming
42 ? <Spinner size='8x' spinning={'spinning'} />
43 : <Link to='/'>
44 <Spinner size='8x' spinning={''} />
45 </Link>
46 }
47 </div>
48}
فایل Spinner.js
اگر به کدهایی که در بخش قبلی مطرح کردیم، به قدر کافی دقت کرده باشید، متوجه شدهاید که ما در چهار موقعیت مختلف از کامپوننت < / Spinner> استفاده کردهایم. تنها چیزی که در این دفعات تغییر یافته است props است که به کامپوننت ارسال میشود.
Spinner.js بازخورد را در موقعیتهای زیر ارائه میکند:
- هنگامی که اپلیکیشن بارگذاری میشود.
- درون دکمه، هنگامی که فشرده میشود.
- هنگامی که لینک ایمیل کلیک میشود و سرور تأیید میکند.
- زمانی که دکمه ریاستارت کردن کل فرایند، فشرده میشود.
همه این موارد تنها در 8 خط از کد React نوشته شدهاند.
1import React from 'react'
2import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
3import { faSync } from '@fortawesome/free-solid-svg-icons'
4
5export default props =>
6 <div className={`fadeIn ${props.spinning}`}>
7 <FontAwesomeIcon icon={faSync} size={props.size} />
8 </div>
فایل Spinner.js انتهای کد کلاینت است. البته متوجه هستیم که این همه کد برای یک وظیفه ساده برای تأیید آدرس ایمیل زیاد به نظر میرسد. در واقع اگر بخش بازخورد و کامنت ها را حذف کنیم، تقریباً نیمی از حجم این کد کاسته میشود؛ اما در این حالت تجربه کاربری نیز دقیقاً نصف میشود!
نکته مهم این است که کاربر را در جریان کارهایی که در حال اجرا هستند قرار دهیم و به علاوه اپلیکیشنی داشته باشیم که پیش از آن که کاربر بخواهد کاری انجام دهد، کاملاً بارگذاری شده و آماده اجرا باشد.
گرچه ارائه بازخورد موجب میشود که حجم کد افزایش یابد، اما از سوی دیگر موجب افزایش کیفیت تجربه کاربری میشود. اینک باید کاری کنیم که کلاینت و سرور بتوانند با هم صحبت کنند.
نوشتن کد سرور
در این بخش کدهای مورد نیاز در سمت سرور را مینویسیم.
سرور
جهت راهاندازی و اجرا کردن سرور باید چندین مرحله از تنظیمات را طی کنیم:
- یک آدرس ایمیل ایجاد کند تا برای این اپلیکیشن مورد استفاده قرار دهید.
- Mongo را اجرا کنید تا مکانی برای ذخیره آدرسهای ایمیل داشته باشیم.
دریافت یک آدرس Gmail جدید
شما میتواند از این لینک (+) برای ثبت یک حساب کاربری جدید جیمیل اقدام کنید. دقت کنید که نباید از یک حساب جیمیل که برایتان مهم است استفاده کنید، چون اطلاعات رمز عبور آن در یک فایل env. روی نقاط مختلف سیستم ذخیره خواهد شد و علاوه بر آن چنان که در ادامه اشاره خواهیم کرد، امکان اجرای اپلیکیشنهای با امنیت کمتر (allow less secure apps) را در آن فعال خواهیم کرد. از این رو پیشگیری بهتر از درمان است و یک حساب کاربری جیمیل جدید میسازیم.
شما میتوانید این اپلیکیشن را با استفاده از آدرس ایمیلی از هر ارائه دهنده سرویس ایمیل مورد استفاده قرار دهید. برخی تنظیمات ممکن است در فایل sendEmail.js که در ادامه آمده است، اندکی متفاوت باشد و این تنظیمات به مشخصات ارائه دهنده ایمیل وابسته است. ما به منظور حفظ سادگی فرایند، در این راهنما از Gmail استفاده میکنیم.
پس از این که حساب جیمیل خود را ساختید، باید اطلاعات احراز هویت برای این حساب کاربری جدید را در یک فایل env. روی سرور قرار دهید. ما از فایل server/.env استفاده میکنیم و محتوای آن چنین خواهد بود:
MAIL_USER=your_new_email_address@gmail.com MAIL_PASS=your_new_password
نکته مهم: برای این که حساب جیمیل جدیدی که ساختهاید بتواند به نیابت از شما ایمیلهایی ارسال کند و بدین ترتیب اپلیکیشن ما بتواند کار کند باید امکان «Less secure app access» را فعال کنیم. مراحل این کار در این صفحه پشتیبانی گوگل (+) توضیح داده شده است.
اجرا کردن Mongo
اگر از قبل Mongo را به صورت محلی نصب کرده باشید، اینک میدانید که باید از دستور زیر استفاده کنید:
$ mongod
فایل user.model.js
در مدل Mongoose ما تنها دو چیز را ردگیری میکنیم. یک آدرس ایمیل و این که آیا تأیید شده است یا نه.
1const mongoose = require('mongoose')
2const Schema = mongoose.Schema
3
4// Data we need to collect/confirm to have the app go.
5const fields = {
6 email: {
7 type: String
8 },
9 confirmed: {
10 type: Boolean,
11 default: false
12 }
13}
14
15// One nice, clean line to create the Schema.
16const userSchema = new Schema(fields)
17
18module.exports = mongoose.model('User', userSchema)
سلام و وقت بخیر؛
برای بازیابی نشانی ایمیل، میبایست گزینه «فراموش کردن گذرواژه» یا عبارتهای مشابه را در پرووایدرهای مختلف کلیک کنید. به این ترتیب با استفاده از روشهای مختلفی که برای راستیآزمایی ادعای مالکیت شما روی نشانی مورد نظر وجود دارد، میتوانید نشانی ایمیل خود را بازیابی نمایید.
از توجه شما متشکرم.
سلام عرض خسته نباشید خدمت شما هز چی کوشیش می کنم ایمیل من باز نمیشه چند مرحله پیش میرم اما باز نمیشه چون قبلا به گوشی دگه من بوده که دگه قابل تعمیر نمیشه و ایمیل من باز نمیشه میشود یک کاری برای من بکنید