diff --git a/.env.example b/.env.example
index e90b778..d9bda0e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,3 @@
REACT_APP_WEB3_STORAGE_API_TOKEN=
+REACT_APP_WEB3_STORAGE_API_DB_TOKEN=
REACT_APP_WEB3_NAME=
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 254d9e3..4cec8d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"process": "^0.11.10",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
+ "react-avatar": "^5.0.3",
"react-dom": "^18.2.0",
"react-error-overlay": "^6.0.11",
"react-icons": "^4.4.0",
@@ -7276,6 +7277,14 @@
"node": ">=10"
}
},
+ "node_modules/charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/check-types": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
@@ -7841,6 +7850,14 @@
"node": ">= 8"
}
},
+ "node_modules/crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@@ -12415,6 +12432,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
"node_modules/is-callable": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
@@ -12661,6 +12683,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-retina": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
+ "integrity": "sha512-/tCmbIETZwCd8uHWO+GvbRa7jxwHFHdfetHfiwoP0aN9UDf3prUJMtKn7iBFYipYhqY1bSTjur8hC/Dakt8eyw=="
+ },
"node_modules/is-retry-allowed": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
@@ -15588,6 +15615,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/md5": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+ "dependencies": {
+ "charenc": "0.0.2",
+ "crypt": "0.0.2",
+ "is-buffer": "~1.1.6"
+ }
+ },
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -18820,6 +18857,21 @@
"semver": "bin/semver"
}
},
+ "node_modules/react-avatar": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-5.0.3.tgz",
+ "integrity": "sha512-DNc+qkWH9QehSEZqHBhqpXWsPY+rU9W7kD68QFHfu8Atfsvx/3ML0DzAePgTUd96nCXQQ3KZMcC3LKYT8FiBIg==",
+ "dependencies": {
+ "is-retina": "^1.0.3",
+ "md5": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@babel/runtime": ">=7",
+ "core-js-pure": ">=3",
+ "prop-types": "^15.0.0 || ^16.0.0",
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@@ -28711,6 +28763,11 @@
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="
},
+ "charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
+ },
"check-types": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz",
@@ -29171,6 +29228,11 @@
"which": "^2.0.1"
}
},
+ "crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
+ },
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@@ -32714,6 +32776,11 @@
"has-tostringtag": "^1.0.0"
}
},
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
"is-callable": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
@@ -32866,6 +32933,11 @@
"resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
"integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="
},
+ "is-retina": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
+ "integrity": "sha512-/tCmbIETZwCd8uHWO+GvbRa7jxwHFHdfetHfiwoP0aN9UDf3prUJMtKn7iBFYipYhqY1bSTjur8hC/Dakt8eyw=="
+ },
"is-retry-allowed": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
@@ -35075,6 +35147,16 @@
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="
},
+ "md5": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+ "requires": {
+ "charenc": "0.0.2",
+ "crypt": "0.0.2",
+ "is-buffer": "~1.1.6"
+ }
+ },
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -37317,6 +37399,15 @@
}
}
},
+ "react-avatar": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-5.0.3.tgz",
+ "integrity": "sha512-DNc+qkWH9QehSEZqHBhqpXWsPY+rU9W7kD68QFHfu8Atfsvx/3ML0DzAePgTUd96nCXQQ3KZMcC3LKYT8FiBIg==",
+ "requires": {
+ "is-retina": "^1.0.3",
+ "md5": "^2.0.0"
+ }
+ },
"react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
diff --git a/package.json b/package.json
index f81211b..ea4bbef 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"process": "^0.11.10",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
+ "react-avatar": "^5.0.3",
"react-dom": "^18.2.0",
"react-error-overlay": "^6.0.11",
"react-icons": "^4.4.0",
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 9da4b47..13aa399 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -8,6 +8,8 @@ interface IBlog {
Body: string;
Creator: string;
Followers: string[];
+ CID?: string;
+ Date?: Date;
}
interface IUser {
@@ -21,3 +23,8 @@ interface IDatabase {
Blogs: string[];
Users: IUser[];
}
+
+interface IDatabaseAction {
+ Type: "ADD_BLOG" | "ADD_USER";
+ CID: string;
+}
diff --git a/src/App.tsx b/src/App.tsx
index 2b900b5..d207b91 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,12 +5,14 @@ import {NotificationContainer} from 'components/Notification';
import 'react-notifications/lib/notifications.css';
import Loader from 'components/Loader';
import { useSelector } from 'react-redux';
+import SyncWorker from 'components/SyncWorker';
function App() {
// const loading = useSelector((store: any) => store.viewStates.loading);
return (
+
{/* {loading &&
} */}
diff --git a/src/components/BlogComponent.tsx b/src/components/BlogComponent.tsx
new file mode 100644
index 0000000..dfb8164
--- /dev/null
+++ b/src/components/BlogComponent.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Avatar from 'react-avatar';
+
+interface IBlogComponent {
+ blog: IBlog;
+}
+
+const BlogComponent = (props: IBlogComponent) => {
+ const { blog } = props;
+
+ return (
+
+
+
+
User
+
{blog.Date?.toLocaleString() || ""}
+
+
{blog.Title}
+
+
+ );
+};
+
+export default BlogComponent;
diff --git a/src/components/Home.tsx b/src/components/Home.tsx
index bac306e..84d9c4d 100644
--- a/src/components/Home.tsx
+++ b/src/components/Home.tsx
@@ -1,11 +1,15 @@
import React from 'react';
+import { useSelector } from 'react-redux';
+import BlogComponent from './BlogComponent';
interface IHome {}
const Home = (props: IHome) => {
+ const blogs = useSelector
(state => state.db.Blogs) || [];
+
return (
-
- Home
+
+ {blogs.map(blog => ())}
);
};
diff --git a/src/components/NewBlog.tsx b/src/components/NewBlog.tsx
index 73ff96f..bacba71 100644
--- a/src/components/NewBlog.tsx
+++ b/src/components/NewBlog.tsx
@@ -34,9 +34,9 @@ const NewBlog = (props: INewBlog) => {
};
dispatch(createBlog(blog)).then(res => {
- alert('res');
+ console.log({res});
}, err => {
- alert('err');
+ console.log({err});
});
};
diff --git a/src/components/SyncWorker.tsx b/src/components/SyncWorker.tsx
new file mode 100644
index 0000000..c0c3900
--- /dev/null
+++ b/src/components/SyncWorker.tsx
@@ -0,0 +1,75 @@
+import React, { useEffect } from "react";
+import { Service, Upload, Web3Storage } from "web3.storage";
+import config from "utils/config";
+import api from "utils/api";
+import { getStorageItem, getW3link, setStorageItem, sleep } from "utils/helper";
+import { AppDispatch } from "slices/store";
+import { useDispatch } from "react-redux";
+import { resetFromLocalStorage } from "slices/dbSlice";
+
+interface ISyncWorker {}
+
+const SyncWorker = (props: ISyncWorker) => {
+ const dispatch = useDispatch
();
+ let blogs: IBlog[] = [];
+
+ useEffect(() => {
+ sync();
+ }, []);
+
+ const sync = async () => {
+ const clientDb = new Web3Storage({
+ token: config.REACT_APP_WEB3_STORAGE_API_DB_TOKEN,
+ } as Service);
+
+ const uploadObjects: Upload[] = [];
+ for await (const upload of clientDb.list()) {
+ uploadObjects.push(upload);
+ }
+
+ uploadObjects.sort((a, b) => (a.created > b.created) ? 1 : -1)
+
+ blogs = getStorageItem(config.LAST_SYNC_BLOGS, []) || [];
+ const lastSyncNumber = getStorageItem(config.LAST_SYNC_NUMBER, 0) || 0;
+
+ console.log({blogs, lastSyncNumber});
+ for (let i = lastSyncNumber; i < uploadObjects.length; i++) {
+ const res = await clientDb.get(uploadObjects[i].cid);
+ if (!res) {
+ continue;
+ }
+
+ const files = await res.files();
+ for (const file of files) {
+ const contentRes = await api.get(getW3link(file.cid));
+ await doDbAction(contentRes.data, uploadObjects[i]);
+ }
+
+ setStorageItem(config.LAST_SYNC_NUMBER, i + 1);
+ }
+
+ blogs = [];
+ dispatch(resetFromLocalStorage());
+ };
+
+ // dispose individual action
+ const doDbAction = async (action: IDatabaseAction, upload: Upload) => {
+ console.log(action);
+
+ switch (action.Type) {
+ case "ADD_BLOG":
+ const res = await api.get(getW3link(action.CID));
+ blogs.push({
+ ...res.data,
+ CID: action.CID,
+ Date: new Date(upload.created)
+ });
+ setStorageItem(config.LAST_SYNC_BLOGS, blogs);
+ break;
+ }
+ }
+
+ return null;
+};
+
+export default SyncWorker;
diff --git a/src/slices/dbSlice.ts b/src/slices/dbSlice.ts
index 128aa51..f925f2b 100644
--- a/src/slices/dbSlice.ts
+++ b/src/slices/dbSlice.ts
@@ -1,11 +1,11 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
-import { convertTitle } from 'utils/helper';
+import { convertTitle, getStorageItem } from 'utils/helper';
import { Service, Web3Storage } from 'web3.storage';
-import * as Name from 'w3name';
import config from 'utils/config';
const client = new Web3Storage({ token: config.REACT_APP_WEB3_STORAGE_API_TOKEN } as Service);
-const name = Name.parse(config.REACT_APP_WEB3_NAME);
+const clientDb = new Web3Storage({ token: config.REACT_APP_WEB3_STORAGE_API_DB_TOKEN } as Service);
+
const initialState = {
Blogs: [] as string[],
@@ -15,37 +15,46 @@ const initialState = {
export const createBlog = createAsyncThunk('createBlog', async (blog: IBlog) => {
try {
const title = convertTitle(blog.Title);
- const blob = new File([JSON.stringify(blog, null, 2)], title, {type: "application/json"})
+ let blob = new File([JSON.stringify(blog, null, 2)], title + "_1", {type: "application/json"})
const rootCid = await client.put([blob], {
name: title,
maxRetries: 3,
+ wrapWithDirectory: false
});
- const name = Name.parse(config.REACT_APP_WEB3_NAME);
- const revision = await Name.resolve(name);
-
+ const dbAction: IDatabaseAction = {
+ Type: "ADD_BLOG",
+ CID: rootCid
+ };
+
+ blob = new File([JSON.stringify(dbAction, null, 2)], title + "_1", {type: "application/json"})
+ await clientDb.put([blob], {
+ name: "add_blog_" + title,
+ maxRetries: 3,
+ wrapWithDirectory: false
+ });
- // const nextRevision = await Name.increment(revision, nextValue);
- // await Name.publish(nextRevision, name.key);
+ return blog;
} catch {
- return null;
+ return false;
}
});
-
export const dbSlice = createSlice({
name: 'dbSlice',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
- reducers: {},
+ reducers: {
+ resetFromLocalStorage: (state: IDatabase, action: PayloadAction) => {
+ state.Blogs = getStorageItem(config.LAST_SYNC_BLOGS, []) || [];
+ }
+ },
extraReducers: (builder) => {
builder.addCase(createBlog.fulfilled, (state: any, action: PayloadAction) => {
- state.web3 = action.payload?.web3;
- console.log({ action });
- state.selectedAddress = action.payload?.account;
- state.chainId = action.payload?.chainId;
});
},
});
+export const {resetFromLocalStorage} = dbSlice.actions;
+
export default dbSlice.reducer;
diff --git a/src/slices/viewState.ts b/src/slices/viewState.ts
index 38f3d9d..eb75aae 100644
--- a/src/slices/viewState.ts
+++ b/src/slices/viewState.ts
@@ -5,8 +5,8 @@ const initialState = {
loading: false
};
-export const web3Slice = createSlice({
- name: 'web3',
+export const viewState = createSlice({
+ name: 'viewState',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
@@ -18,6 +18,6 @@ export const web3Slice = createSlice({
},
});
-export const {setLoading} = web3Slice.actions;
+export const {setLoading} = viewState.actions;
-export default web3Slice.reducer;
+export default viewState.reducer;
diff --git a/src/styles/app.scss b/src/styles/app.scss
index c42a76e..3f9e0b2 100644
--- a/src/styles/app.scss
+++ b/src/styles/app.scss
@@ -4,6 +4,7 @@
@import './components/footer';
@import './components/navbar';
@import './components/new_blog_page';
+@import './components/blog_component';
@import './pages/home_layout';
@import './pages/welcome_page';
diff --git a/src/styles/components/_blog_component.scss b/src/styles/components/_blog_component.scss
new file mode 100644
index 0000000..878aa7f
--- /dev/null
+++ b/src/styles/components/_blog_component.scss
@@ -0,0 +1,35 @@
+.blog-item {
+ padding: 20px;
+ border-bottom: 1px solid #ccc;
+
+ .user {
+ display: flex;
+ align-items: center;
+
+ .name {
+ margin: 0 0 0 6px;
+ font-size: 14px;
+ color: #666;
+ cursor: pointer;
+ }
+
+ .date {
+ margin: 0 0 0 auto;
+ font-size: 12px;
+ color: #666;
+ cursor: pointer;
+ }
+ }
+
+ .blog-title {
+ margin: 12px 0 8px 0;
+ line-height: 28px;
+ font-size: 22px;
+ cursor: pointer;
+ }
+
+ .blog-body {
+ font-size: 16px;
+ cursor: pointer;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/pages/_home_layout.scss b/src/styles/pages/_home_layout.scss
index 2420aec..0bc8299 100644
--- a/src/styles/pages/_home_layout.scss
+++ b/src/styles/pages/_home_layout.scss
@@ -19,9 +19,10 @@
border: 1px solid #ddd;
border-top: none;
border-bottom: none;
- height: 100%;
+ height: 100vh;
width: 70%;
height: 100vh;
- padding: 35px 10px 10px 10px;
+ padding: 20px 10px 10px 10px;
+ overflow-y: auto;
}
}
diff --git a/src/utils/api.ts b/src/utils/api.ts
index 02d431d..ae8ede9 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -1,8 +1,10 @@
-import axios from 'axios';
+import axios from "axios";
const api = axios.create({
params: {},
- headers: {},
+ headers: {
+ "content-type": "application/json;charset=UTF-8",
+ },
});
export function sendFormData(url: string, method: string, FD: FormData) {
@@ -10,7 +12,7 @@ export function sendFormData(url: string, method: string, FD: FormData) {
const XHR = new XMLHttpRequest();
// Define what happens on successful data submission
- XHR.addEventListener('load', function (event: any) {
+ XHR.addEventListener("load", function (event: any) {
if (event.target.status >= 200 && event.target.status < 300) {
resolve(event.target.response);
} else {
@@ -19,7 +21,7 @@ export function sendFormData(url: string, method: string, FD: FormData) {
});
// Define what happens in case of error
- XHR.addEventListener(' error', function (event: any) {
+ XHR.addEventListener(" error", function (event: any) {
reject(event.target.response);
});
diff --git a/src/utils/config.ts b/src/utils/config.ts
index d703924..63d1a2d 100644
--- a/src/utils/config.ts
+++ b/src/utils/config.ts
@@ -1,7 +1,10 @@
const config = {
- appID: 'ipp',
+ appID: 'IPP',
REACT_APP_WEB3_STORAGE_API_TOKEN: process.env.REACT_APP_WEB3_STORAGE_API_TOKEN || "",
+ REACT_APP_WEB3_STORAGE_API_DB_TOKEN: process.env.REACT_APP_WEB3_STORAGE_API_DB_TOKEN || "",
REACT_APP_WEB3_NAME: process.env.REACT_APP_WEB3_NAME || "",
+ LAST_SYNC_NUMBER: "IPP_LAST_SYNC_NUMBER",
+ LAST_SYNC_BLOGS: "IPP_LAST_SYNC_BLOGS",
};
export default config;
diff --git a/src/utils/helper.ts b/src/utils/helper.ts
index caae8e4..7041e7b 100644
--- a/src/utils/helper.ts
+++ b/src/utils/helper.ts
@@ -96,3 +96,15 @@ export function convertTitle(str: string) {
}
return res;
}
+
+export function getW3link(cid: string) {
+ // return `https://w3s.link/ipfs/${cid}`;
+ // return `https://${cid}.ipfs.w3s.link/`;
+ return `https://braindance.mypinata.cloud/ipfs/${cid}`;
+}
+
+export function sleep(ms: number) {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => resolve(true), ms);
+ })
+}