原文:https://dev.to/dkb868/build-a-decentralized-todo-list-with-react-and-blockstack-59cm
沒(méi)翻譯完,請(qǐng)看 http://www.itdecent.cn/p/078c1dae4397
在線例子:
https://rebase-todolist.netlify.com
npm run eject
在本教程中,你將學(xué)習(xí)使用Blockstack和React構(gòu)建去中心化的Todolist。Blockstack是一個(gè)平臺(tái),它使構(gòu)建Dapp變得非常容易,與傳統(tǒng)的auth/storage方法相比,使用Blockstack身份驗(yàn)證和存儲(chǔ)構(gòu)建一個(gè)簡(jiǎn)單的App更快、更安全。
Blockstack的去中心化方法
像Google和Facebook這樣的大公司都有中心化的數(shù)據(jù)庫(kù),它們可以控制你的數(shù)據(jù),并且可以對(duì)數(shù)據(jù)做任何他們想做的事情。
Blockstack App允許用戶完全控制自己的數(shù)據(jù)。沒(méi)有用戶的允許,任何人都不能訪問(wèn)用戶的數(shù)據(jù)。用戶數(shù)據(jù)被加密并存儲(chǔ)在私人的“數(shù)據(jù)鎖”中,用戶可以給App權(quán)限來(lái)讀取或?qū)懭霐?shù)據(jù)到他們的存儲(chǔ)中。
在我們的Todolist App中,這意味著App開(kāi)發(fā)人員永遠(yuǎn)不會(huì)知道你的Todolist上有什么。
The App
我們的Todolist將非常簡(jiǎn)單,這樣我們就可以專注于學(xué)習(xí)Blockstack是如何工作的。
完成后的App是這個(gè)樣子:
Todolist App
demo 網(wǎng)址: https://blockstack-todo-list.netlify.com/
Github 網(wǎng)址: https://github.com/dkb868/secure-todo-list
The Setup
首先,我們將設(shè)置環(huán)境,安裝node.js的最新版本。
React
我們使用 create-react-app, 在你命令行窗口輸入 npx create-react-app secure-todo-list 創(chuàng)建新的項(xiàng)目。
大約一分鐘后,就應(yīng)該完成了。
控制臺(tái)
進(jìn)入項(xiàng)目 cd secure-todo-list 然后輸入 npm start 啟動(dòng)項(xiàng)目
Then open up the project folder in your coding editor and let's do some cleanup. Delete the following files:
App.css
App.test.js
index.css
logo.svg
然后打開(kāi)App.js,將內(nèi)容替換為:
import React from "react"
class App extends React.Component {
render() {
return <div>Nice Meme</div>
}
}
export default App
并更新 index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import * as serviceWorker from "./serviceWorker"
ReactDOM.render(<App />, document.getElementById("root"))
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()
Prettier 插件
如果你不使用Prettier,我強(qiáng)烈推薦它。它使你的代碼更干凈。你可以通過(guò)尋找Prettier插件將其添加到編輯器中。
將.prettierrc文件添加到項(xiàng)目根目錄(secure-todo-list/),內(nèi)容為空,這將提供默認(rèn)設(shè)置。
{}
Semantic UI
我們將使用Semantic UI,一個(gè)CSS庫(kù),給我們的應(yīng)用程序一些樣式。
將這個(gè)url (https://cdnjs.cloudflare.com/ajax/libs/semanticui/2.4.1 /semantic.min.css)復(fù)制到你的public/index.html中,方法是將這一行添加到html文件的頭部。
<link
rel="stylesheet"
/>
現(xiàn)在,你應(yīng)該已經(jīng)完成了一個(gè)非常漂亮的、極簡(jiǎn)主義的網(wǎng)站。
Blockstack 賬戶
你需要一個(gè)Blockstack帳戶 以便你可以登錄和使用App。你可以通過(guò)https://blockstack.org/并從菜單中選擇Create ID獲取一個(gè)賬戶。
Blockstack
一個(gè)簡(jiǎn)單的 Todo List
首先,我們用React構(gòu)建一個(gè)簡(jiǎn)單的Todolist,其中不包含任何Blockstack技術(shù)。每當(dāng)頁(yè)面刷新時(shí), App狀態(tài)將丟失,這將使它更容易看到Blockstack的作用。
初始化狀態(tài)
我們添加一些狀態(tài)到我們的App。在App.js的render函數(shù)上面添加內(nèi)容:
state = {
todos: [
{
id: 1,
title: "Wash the dishes",
done: false,
},
{
id: 2,
title: "Clean my room",
done: false,
},
],
}
現(xiàn)在我們的應(yīng)用程序跟蹤todos,它有三個(gè)屬性:
- id:todo的唯一標(biāo)識(shí)符
- title: 任務(wù)的名稱
- done:是否已完成這個(gè)任務(wù)
顯示Todos
現(xiàn)在我們有了一些todo,讓我們?cè)陧?yè)面上顯示它們。
改變render方法,代碼如下:
render() {
return (
<div style={{ padding: "30px 0" }}
className="ui text container center aligned">
<h2>My Todos</h2>
<div className="ui grid">
<div className="row centered">
<div className="column twelve wide">
<div className="grouped fields">
{this.state.todos
.filter(todo => !todo.done)
.map(todo => (
<div key={todo.id} className="field">
<div className="ui checkbox">
<input type="checkbox" />
<label>{todo.title}</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
所有的類名,比如 ui text container center aligned,都來(lái)自 Semantic UI,幫助我們的應(yīng)用程序看起來(lái)更好。
這 1 行 this.state.todos.filter(todo => !todo.done).map(todo => ... 過(guò)濾掉已經(jīng)完成的待辦事項(xiàng),并將它們隱藏在頁(yè)面之外。
現(xiàn)在有一個(gè)看起來(lái)像待辦事項(xiàng)列表的東西。
todolist
如果單擊其中一個(gè)復(fù)選框,就會(huì)發(fā)現(xiàn)什么也不發(fā)生。理想情況下,我們想讓東西在檢查時(shí)消失,所以我們把它加進(jìn)去。
完成 Todos
Add an onClick handler to the checkbox.
在復(fù)選框中添加一個(gè)onClick處理程序。
<input
type="checkbox"
onClick={() => {
this.handleCheckboxClick(todo.id)
}}
/>
我們使用了一個(gè)稍微奇怪的語(yǔ)法,因?yàn)槲覀兿M麑⑺xtodo的id傳遞給處理函數(shù)。
處理程序應(yīng)該添加到render函數(shù)之上。
handleCheckboxClick(id) {
let newTodos = [...this.state.todos];
newTodos[newTodos.findIndex(todo => todo.id === id)].done = true;
this.setState({
todos: newTodos
});
}
這是React中修改數(shù)組狀態(tài)的多種方法之一。首先復(fù)制當(dāng)前todos列表,然后將選擇的todo(通過(guò)其id標(biāo)識(shí))標(biāo)記為done并更新?tīng)顟B(tài)。
現(xiàn)在,當(dāng)你選中復(fù)選框時(shí),todo就會(huì)從頁(yè)面中消失,因?yàn)槲覀儗⑦^(guò)濾掉標(biāo)記為done的任何todo 項(xiàng)。
添加 Todos
在現(xiàn)實(shí)生活中,人們可能有比洗碗和打掃房間更多的任務(wù)要做,所以讓我們?cè)试S用戶添加他們自己的待辦事項(xiàng)。
首先,向render方法添加一個(gè)輸入表單。
render() {
return (
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<h2>My Todos</h2>
<div className="ui grid">
<div className="row centered">
<div className="column twelve wide">
<form className="ui form" onSubmit={this.handleAddTodoClick}>
<div className="inline fields">
<div className="twelve wide field">
<input
type="text"
value={this.state.newTodo}
onChange={this.hanldeInputChange}
/>
</div>
<button className="ui button primary" type="submit">
Add todo
</button>
</div>
</form>
</div>
</div>
<div className="row centered">
<div className="column twelve wide">
<div className="grouped fields">
{this.state.todos
.filter(todo => !todo.done)
.map(todo => (
<div key={todo.id} className="field">
<div className="ui checkbox">
<input
type="checkbox"
onClick={() => {
this.handleCheckboxClick(todo.id);
}}
/>
<label>{todo.title}</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
然后讓我們實(shí)現(xiàn)所有這些處理函數(shù)。
更新初始狀態(tài)以跟蹤新的todo值,并清除那些默認(rèn)的todo。
state = {
todos: [],
newTodo: "",
}
實(shí)現(xiàn)handleInputChange函數(shù),該函數(shù)將跟蹤用戶輸入的內(nèi)容。
hanldeInputChange = e => {
this.setState({
newTodo: e.target.value,
})
}
接下來(lái),我們實(shí)現(xiàn)handleAddTodoClick,當(dāng)用戶單擊enter或單擊按鈕來(lái)添加他們的新todo項(xiàng)時(shí),將調(diào)用handleAddTodoClick。
handleAddTodoClick = e => {
e.preventDefault()
const newTodo = {
id: this.state.todos.length + 1,
title: this.state.newTodo,
done: false,
}
const todos = [...this.state.todos]
todos.push(newTodo)
this.setState({
todos: todos,
newTodo: "",
})
}
你的整個(gè)App.js應(yīng)該是這樣的:
import React from "react"
class App extends React.Component {
state = {
todos: [],
newTodo: "",
}
handleCheckboxClick(id) {
let newTodos = [...this.state.todos]
newTodos[newTodos.findIndex(todo => todo.id === id)].done = true
this.setState({
todos: newTodos,
})
}
handleAddTodoClick = e => {
e.preventDefault()
const newTodo = {
id: this.state.todos.length + 1,
title: this.state.newTodo,
done: false,
}
const todos = [...this.state.todos]
todos.push(newTodo)
this.setState({
todos: todos,
newTodo: "",
})
}
hanldeInputChange = e => {
this.setState({
newTodo: e.target.value,
})
}
render() {
return (
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<h2>My Todos</h2>
<div className="ui grid">
<div className="row centered">
<div className="column twelve wide">
<form className="ui form" onSubmit={this.handleAddTodoClick}>
<div className="inline fields">
<div className="twelve wide field">
<input
type="text"
value={this.state.newTodo}
onChange={this.hanldeInputChange}
/>
</div>
<button className="ui button primary" type="submit">
Add todo
</button>
</div>
</form>
</div>
</div>
<div className="row centered">
<div className="column twelve wide">
<div className="grouped fields">
{this.state.todos
.filter(todo => !todo.done)
.map(todo => (
<div key={todo.id} className="field">
<div className="ui checkbox">
<input
type="checkbox"
onClick={() => {
this.handleCheckboxClick(todo.id)
}}
/>
<label>{todo.title}</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
}
}
export default App
現(xiàn)在,你應(yīng)該能夠添加新的待辦事項(xiàng),并勾選它們。唯一的問(wèn)題是,當(dāng)你刷新頁(yè)面時(shí),你會(huì)丟失所有珍貴的待辦事項(xiàng)?,F(xiàn)在是時(shí)候使用Blockstack來(lái)保存我們的todo了。
我們加上 Blockstack
現(xiàn)在,我們將使用Blockstack添加用戶身份驗(yàn)證和存儲(chǔ)。先停止App,安裝 npm install blockstack,然后我們可以重新啟動(dòng)應(yīng)用程序npm start。
身份認(rèn)證
在blockstack 的App.js的類聲明的上方添加以下行:
import { UserSession, AppConfig } from "blockstack";
const appConfig = new AppConfig(["store_write"]);
const userSession = new UserSession({ appConfig: appConfig });
class App extends React.Component {
...
}
這一行const appConfig = new AppConfig(["store_write"]);用于設(shè)置Blockstack App 的配置。 你要向用戶請(qǐng)求所需的權(quán)限。在這個(gè)例子中,我們請(qǐng)求store_write權(quán)限,它允許我們將數(shù)據(jù)存儲(chǔ)在用戶的私有存儲(chǔ)中。
如果我們想構(gòu)建社交類的App,我們需要publish_data權(quán)限,它允許某些用戶數(shù)據(jù)對(duì)其他用戶可見(jiàn)。
const userSession = new UserSession({ appConfig: appConfig });
建立用戶session,允許我們處理身份驗(yàn)證。
在頁(yè)面頂部添加一個(gè)登錄按鈕。
<div style={{ padding: "30px 0" }} className="ui text container center aligned">
<button className="ui button positive" onClick={this.handleSignIn}>
Sign in with blockstack
</button>
<h2>My Todos</h2>
...
</div>
And implement our handler function this.handleSignIn like this:
并實(shí)現(xiàn)函數(shù) this.handleSignIn:
handleSignIn = () => {
userSession.redirectToSignIn()
}
實(shí)現(xiàn)登錄只需要一行代碼,頁(yè)面現(xiàn)在應(yīng)該是這樣的:
登錄頁(yè)面
我們點(diǎn)擊那個(gè)按鈕,看看會(huì)發(fā)生什么!
我們被帶到blockstack瀏覽器登錄,但看起來(lái)有一個(gè)問(wèn)題
提示錯(cuò)誤
提示"Failed to fetch information about the app requesting authentication. Please contact the app maintainer to resolve the issue." 不知道在說(shuō)什么,但控制臺(tái)顯示了一些更有用的東西。
Access to fetch at 'http://localhost:3000/manifest.json'
from origin 'https://browser.blockstack.org' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
這實(shí)際上這是一個(gè)很常見(jiàn)的bug。
解決 CORS 問(wèn)題
這個(gè)問(wèn)題是Blockstack瀏覽器試圖從你的網(wǎng)站訪問(wèn)一個(gè)名為manifest.json的文件,其中包含App的一些信息。由于CORS,在默認(rèn)情況下,網(wǎng)站在不同的域不能向其他網(wǎng)站發(fā)出請(qǐng)求 。這樣做是出于安全。所以我們的網(wǎng)站現(xiàn)在拒絕了Blockstack瀏覽器對(duì)我們manifest.json的請(qǐng)求。實(shí)際上希望Blockstack能夠訪問(wèn)該文件。
To do that, we'll need to modify our webpack config. Since we used create-react-app , the webpack config is hidden. To modify it, we use the command npm run eject. You will probably get a warning about having untracked files and uncommitted changes. So commit all your changes to git first.
為此,我們需要修改webpack配置。因?yàn)槲覀兪褂昧?code>create- response -app,webpack配置是隱藏的。要修改它,我們使用命令npm run eject。就會(huì)得到一個(gè)關(guān)于git的警告。因此,首先將所有更改提交到git。
git add -A
git commit -m "did things"
npm run eject
git提交
You'll see two new folders in your directory called scripts and config. Go to config/webpackDevServer.config.js and add the following line on top of the module exports function.
在目錄中看到兩個(gè)新文件夾scripts和config,在 config / webpackDevServer.config 添加以下行。
module.exports = function(proxy, allowedHost) {
return {
headers: {
"Access-Control-Allow-Origin": "*"
},
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
...
}
}
使用npm start重新啟動(dòng)并重新登錄。
進(jìn)入public/manifest.json,在這里修改 App 的名稱。
{
"short_name": "Todo List",
"name": "Secure Todo List",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
Authentication Continued 繼續(xù)驗(yàn)證
現(xiàn)在,根據(jù)用戶是否登錄來(lái)修改頁(yè)面。退出的用戶不應(yīng)該看到他們的todo列表,而登錄后的用戶不需要看到login按鈕。
To make this a bit cleaner, we're going to separate those two things into different components. We'll have a TodoList component which shows the Todo List and a Login component which shows the login page.
為了是 App更簡(jiǎn)潔,我們把這兩個(gè)東西分成不同的部分。我們將有一個(gè)顯示Todo列表的TodoList組件和一個(gè)顯示登錄頁(yè)面的Login組件。
Copy the contents of App.js into a new file called TodoList.js and modify it as follows.
復(fù)制 App.js的內(nèi)容的到新的頁(yè)面 TodoList.js, 修改如下:
import React from "react"
class TodoList extends React.Component {
state = {
todos: [],
newTodo: "",
}
handleCheckboxClick(id) {
let newTodos = [...this.state.todos]
newTodos[newTodos.findIndex(todo => todo.id === id)].done = true
this.setState({
todos: newTodos,
})
}
handleAddTodoClick = e => {
e.preventDefault()
const newTodo = {
id: this.state.todos.length + 1,
title: this.state.newTodo,
done: false,
}
const todos = [...this.state.todos]
todos.push(newTodo)
this.setState({
todos: todos,
newTodo: "",
})
}
hanldeInputChange = e => {
this.setState({
newTodo: e.target.value,
})
}
render() {
return (
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<h2>My Todos</h2>
<div className="ui grid">
<div className="row centered">
<div className="column twelve wide">
<form className="ui form" onSubmit={this.handleAddTodoClick}>
<div className="inline fields">
<div className="twelve wide field">
<input
type="text"
value={this.state.newTodo}
onChange={this.hanldeInputChange}
/>
</div>
<button className="ui button primary" type="submit">
Add todo
</button>
</div>
</form>
</div>
</div>
<div className="row centered">
<div className="column twelve wide">
<div className="grouped fields">
{this.state.todos
.filter(todo => !todo.done)
.map(todo => (
<div key={todo.id} className="field">
<div className="ui checkbox">
<input
type="checkbox"
onClick={() => {
this.handleCheckboxClick(todo.id)
}}
/>
<label>{todo.title}</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
}
}
export default TodoList
寫一個(gè) Login.js 組件,代碼如下
import React from "react"
class Login extends React.Component {
handleSignIn = () => {
this.props.userSession.redirectToSignIn()
}
render() {
return (
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<h1>Decentralized Todo List</h1>
<p>This is the most secure todo list on the market.</p>
<button className="ui button positive" onClick={this.handleSignIn}>
Sign in with blockstack
</button>
</div>
)
}
}
export default Login
我們將userSession作為props,這個(gè)對(duì)象包含用戶身份驗(yàn)證相關(guān)的函數(shù)。
最后當(dāng)用戶注銷時(shí),App.js 顯示Login組件,當(dāng)用戶登錄后顯示TodoList組件。
import React from "react"
import { UserSession, AppConfig } from "blockstack"
import Login from "./Login"
import TodoList from "./TodoList"
const appConfig = new AppConfig(["store_write"])
const userSession = new UserSession({ appConfig: appConfig })
class App extends React.Component {
render() {
return (
<div>
{userSession.isUserSignedIn() ? (
<TodoList userSession={userSession} />
) : (
<Login userSession={userSession} />
)}
</div>
)
}
}
export default App
We use the function userSession.isUserSignedIn() to find out whether there is a logged in user or not.
Now you should see the login page by default. When you click the button, you are redirected to Blockstack, then once you select your id you are redirected to your app, then...it still shows you the login page. What's up with that?
Turns out we're actually in an intermediary login stage. By this point, Blockstack has given the app a token with all of the user information. We need to add one more function call to extract information from that toke and finish the sign in.
Add these lines above the render() function in your App component.
componentWillMount() {
if (userSession.isSignInPending()) {
userSession
.handlePendingSignIn()
.then(() => {
window.location = window.location.origin;
})
.catch(err => console.log(err));
}
}
This extracts the user information from the token, and completes the sign in, then refreshes the page.
Here is a chart that explains the whole Blockstack authentication process.
With this in place, try logging in again and you should be redirected to the todo list.
Lastly, let's add a sign out button to the todo list page. Go to TodoList.js and add a button to the top of the page in the render function.
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<button className="ui button negative" onClick={this.handleSignout}>
Sign out
</button>
<h2>My Todos</h2>
<div className="ui grid">
...
</div>
</div>
Add the handleSignout function somewhere above the render function.
handleSignout = () => {
this.props.userSession.signUserOut(window.location.origin)
}
Now you can login and logout of the app with Blockstack.
Storing The Todos
Now that the user can login to our app, we can store their data with Blockstack.
We'll be using two core functions of the blockstack.js library: putFile and getFile.
They do exactly what they sound like. putFile allows you to store files, and getFile allows you to retrieve files. You can store any type of file, and they can be encrypted if you want.
In our case, we'll be storing our todos in JSON format because it makes them easy to handle.
Go to TodoList.js and modify the handleAddTodoClick function as follows:
handleAddTodoClick = e => {
e.preventDefault()
const newTodo = {
id: this.state.todos.length + 1,
title: this.state.newTodo,
done: false,
}
const todos = [...this.state.todos]
todos.push(newTodo)
const options = { encrypt: true }
this.props.userSession
.putFile("todos.json", JSON.stringify(todos), options)
.then(() => {
this.setState({
todos,
newTodo: "",
})
})
}
This stores all the user's todos in a file called todos.json
Modify handleCheckboxClick so that when we mark todos as done, this is also updated in the user storage.
handleCheckboxClick(id) {
let newTodos = [...this.state.todos];
newTodos[newTodos.findIndex(todo => todo.id === id)].done = true;
const options = { encrypt: true };
this.props.userSession
.putFile("todos.json", JSON.stringify(newTodos), options)
.then(() => {
this.setState({
todos: newTodos
});
});
}
Try making some todos now and you should see something like this in your console, indicating that the files were stored.
If you refresh the page you won't see anything, because we still need to retrieve the todos.
Add a new function to your class called fetchData which will get the todo list from user storage.
async fetchData() {
const options = { decrypt: true };
const file = await this.props.userSession.getFile("todos.json", options);
let todos = JSON.parse(file || "[]");
this.setState({
todos
});
}
We will call this function in our componentDidMount
componentDidMount() {
this.fetchData();
}
Now you can add a todo item, refresh your page, and it will still be there!
Adding User Profile Data
Right now our app doesn't feel very personal, but we can use Blockstack to get information like the user's name to customize their experience.
Add a new field to the state to store the user object.
state = {
newTodo: "",
todos: [],
user: null,
}
Then modify the fetchData function to update the state with user info.
async fetchData() {
const options = { decrypt: true };
const file = await this.props.userSession.getFile("todos.json", options);
let todos = JSON.parse(file || "[]");
this.setState({
todos,
user: new Person(this.props.userSession.loadUserData().profile)
});
}
And add an import statement at the top of your file.
import { Person } from "blockstack"
The Person object puts the user data in an easily accessible format.
Modify the render function to display some user information. We'll be showing their name and profile image.
render() {
const { user } = this.state;
return (
<div
style={{ padding: "30px 0" }}
className="ui text container center aligned"
>
<button className="ui button negative" onClick={this.handleSignout}>
Sign out
</button>
<h1>{user && user.name()}</h1>
<img
className="ui centered medium rounded image"
src={user && user.avatarUrl()}
alt="user profile image"
/>
<h2>My Todos</h2>
...
Now the app should feature the user's name and profile image.
Our app looks good to go, now let's deploy it for the rest of the world to see.
Deploying To Netlify
There are many ways to deploy your React app, but Netlify is one of the best. It allows you to easily setup continuous deployment.
First let's make a new repository on github.
Add and commit all of your files.
git add -A
git commit -m "made everything"
Then follow the commands to push an existing repository. For me that would be:
git remote add origin https://github.com/dkb868/secure-todo-list.git
git push -u origin master
Now you should have a beautiful new repo up on github.
Make an account on Netlify, then in your dashboard, select "New site from Git".
Select Github, and search for your repo.
Use the following build settings, then click Deploy Site
Give it a few minutes, then you should have your site up at something.netlify.com. You can modify this name if you want, or add a custom domain.
If we go to our newly launched app, we'll see a familiar error.
We know this is a CORS error, and we fixed it in our development environment, so now we need to fix it in production.
With Netlify, this is as simple as adding a netlify.toml file in your root project directory.
[[headers]]
for = "/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Add that file and push it to GitHub. Once you have continuous deploy enabled, it will be deployed automatically in a few minutes.
Now everything should be working great.
Conclusion
If you made it this far, congrats for finishing the app!
If you got lost at some point, you can check out the github repo or the demo website for reference.
Demo Website: https://blockstack-todo-list.netlify.com/
Github Repo: https://github.com/dkb868/secure-todo-list
This is my first coding tutorial, so if you have any feedback on things I can improve, please let me know.
參考:


















