diff --git a/.gitignore b/.gitignore index b2c0670..e663123 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ node_modules/ -frontend/node_modules \ No newline at end of file +frontend/node_modules + +package-lock.json + +frontend/package-lock.json diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ca5bca3..a3b0815 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,36 +1,21 @@ -import React, { useEffect, useState } from 'react'; +import React, {useEffect, useState} from 'react'; import logo from './logo.svg'; import './App.css'; -import { Layout } from 'antd'; +import {Layout} from 'antd'; import Sider from './components/sider'; -import { Outlet, Link } from "react-router-dom"; -import Post from './components/post'; -import Comment from './components/comment'; -import { AuthClient} from "@dfinity/auth-client"; +import {Routes, Route} from "react-router-dom"; +import ErrorPage from './components/errorPage'; +import Explore from './routes/explore'; +import Profile from './routes/profile'; +import Settings from './routes/settings'; +import {Home} from "./routes/home"; -// const { Header, Content, Footer, Sider } = Layout; function App() { - const [authClient, setAuthClient] = useState(); - const [isLogin, setIsLogin] = useState(false); - - const handleLogIn = async () => { - const _authClient = await AuthClient.create(); - _authClient.login({ - maxTimeToLive: BigInt(7 * 24 * 60 * 60 * 1000 * 1000 * 1000), - onSuccess: async () => { - setAuthClient(_authClient); - setIsLogin(true); - }}); - } - - useEffect( () => { - - }, [authClient, isLogin]); return (
- - + - - - - - - - - - - - - - - - - - - - - - + + }/> + }/> + }/> + }/> + }/> + }/> +
); diff --git a/frontend/src/components/sider.tsx b/frontend/src/components/sider.tsx index 2a10cc8..5cc078c 100644 --- a/frontend/src/components/sider.tsx +++ b/frontend/src/components/sider.tsx @@ -1,60 +1,61 @@ -import { Menu, Button, Avatar, Flex, Card, Space } from "antd"; +import {Menu, Button, Avatar, Flex, Card, Space} from "antd"; import { - HomeOutlined, - SettingOutlined, - SearchOutlined, - ProfileOutlined, - UserOutlined + HomeOutlined, + SettingOutlined, + SearchOutlined, + ProfileOutlined, + UserOutlined } from '@ant-design/icons'; -import type { MenuInfo } from 'rc-menu/lib/interface' -import type { MenuItemType } from 'antd/es/menu/hooks/useItems'; -import { useNavigate } from "react-router-dom"; -import { AuthClient} from "@dfinity/auth-client"; +import type {MenuInfo} from 'rc-menu/lib/interface' +import type {MenuItemType} from 'antd/es/menu/hooks/useItems'; +import {useNavigate} from "react-router-dom"; +import {AuthClient} from "@dfinity/auth-client"; import { - getPrincipal, - checkIdentity + getPrincipal, + checkIdentity } from '../utils/common'; import User from '../actors/user'; -import { Principal } from "@dfinity/principal"; -import { Identity } from "@dfinity/agent"; +import {Principal} from "@dfinity/principal"; +import {Identity} from "@dfinity/agent"; +import {useAuth} from "../utils/useAuth"; function getItem( - label: React.ReactNode, - key: string, - icon?: React.ReactNode, + label: React.ReactNode, + key: string, + icon?: React.ReactNode, ): MenuItemType { - return { - key, - icon, - label, - } as MenuItemType; + return { + key, + icon, + label, + } as MenuItemType; } const items: MenuItemType[] = [ - getItem("Home", '1', ), - getItem("Explore", '2', ), - getItem("Profile", '3', ), - getItem("Settings", '4', ), + getItem("Home", '1', ), + getItem("Explore", '2', ), + getItem("Profile", '3', ), + getItem("Settings", '4', ), ]; interface SiderProps { - authClient?: AuthClient; - isLogIn?: Boolean; - handleLogIn?: () => Promise; + authClient?: AuthClient; + isLogIn?: Boolean; + handleLogIn?: Function; } // const getUserProfile = ( @@ -65,89 +66,89 @@ interface SiderProps { // user.actor.getProfile(user_principal) // } -export default function Sider(props: SiderProps) { - const navigate = useNavigate(); - console.log('isLogIn : ', props.isLogIn); - const authClient = props.authClient; - // if(props.authClient != undefined) { - // console.log(props.authClient); - // getUserProfile(props.authClient.getIdentity()); - // } +export default function Sider() { + const navigate = useNavigate(); + const {isAuth, principal, logIn} = useAuth() + console.log('isLogIn : ', isAuth); + // if(props.authClient != undefined) { + // console.log(props.authClient); + // getUserProfile(props.authClient.getIdentity()); + // } - const onClick = (info: MenuInfo) => { - if (info.key === '1') { - navigate('/'); - } else if (info.key === '2') { - navigate('/explore'); - } else if (info.key === '3') { - navigate('/profile'); - } else if (info.key === '4') { - navigate('/settings'); - } + const onClick = (info: MenuInfo) => { + if (info.key === '1') { + navigate('/'); + } else if (info.key === '2') { + navigate('/explore'); + } else if (info.key === '3') { + navigate('/profile'); + } else if (info.key === '4') { + navigate('/settings'); } - - return ( - +
+

-
-

- Proton -

+ Proton +

- + - - { - props.authClient != undefined ? ( -

{props.authClient?.getIdentity().getPrincipal().toString()}

- ) : ( - <> - ) - } + + { + isAuth ? ( +

{principal?.toString()}

+ ) : ( + <> + ) + } -
- { - props.isLogIn ? ( - - } - title="NeutronStarDAO" - description="@NeutronStarDAO" - /> - ) : ( - - - - ) - } -
- ) -} \ No newline at end of file + + { + isAuth ? ( + + } + title="NeutronStarDAO" + description="@NeutronStarDAO" + /> + ) : ( + logIn?.()}> + + + ) + } + + ) +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 2f40028..de0edfc 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -3,32 +3,20 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import ErrorPage from './components/errorPage'; -import Explore from './routes/explore'; -import Profile from './routes/profile'; -import Settings from './routes/settings'; +import { BrowserRouter } from "react-router-dom"; +import {ProvideAuth} from "./utils/useAuth"; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); - root.render( - - } /> - } /> - } /> - } /> - } /> - }/> - + + + ); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); diff --git a/frontend/src/routes/explore.tsx b/frontend/src/routes/explore.tsx index 30c53f0..899ad4c 100644 --- a/frontend/src/routes/explore.tsx +++ b/frontend/src/routes/explore.tsx @@ -1,55 +1,40 @@ import { Layout } from 'antd'; -import Sider from '../components/sider'; import Post from '../components/post'; import Comment from '../components/comment'; export default function Explore() { return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
+ <> + + + + + + + + + + + + + + + + + + + + + ); -} \ No newline at end of file +} diff --git a/frontend/src/routes/home.tsx b/frontend/src/routes/home.tsx new file mode 100644 index 0000000..36cdd49 --- /dev/null +++ b/frontend/src/routes/home.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import {Layout} from "antd"; +import Post from "../components/post"; +import Comment from "../components/comment"; + +export const Home = React.memo(()=>{ + return <> + + + + + + + + + + + + + + + + + + + + + +}) diff --git a/frontend/src/routes/profile.tsx b/frontend/src/routes/profile.tsx index f0c603f..9374576 100644 --- a/frontend/src/routes/profile.tsx +++ b/frontend/src/routes/profile.tsx @@ -1,11 +1,11 @@ -import { useState, useEffect } from 'react'; -import { Layout, Image, Typography, Avatar, Flex, Space, Button, Modal, notification } from 'antd'; +import {useState, useEffect} from 'react'; +import {Layout, Image, Typography, Avatar, Flex, Space, Button, Modal, notification} from 'antd'; import Sider from '../components/sider'; import Post from '../components/post'; -import { AuthClient} from "@dfinity/auth-client"; +import {AuthClient} from "@dfinity/auth-client"; import ProfileForm from '../components/form'; import User from '../actors/user'; -import { Profile } from '../declarations/user/user.did'; +import {Profile} from '../declarations/user/user.did'; type NotificationType = 'success' | 'info' | 'warning' | 'error'; @@ -15,7 +15,7 @@ export default function UserProfile() { const [isModalOpen, setIsModalOpen] = useState(false); const [userProfile, setUserProfile] = useState(); - const handleLogIn = async () => { + const handleLogIn = async () => { const _authClient = await AuthClient.create(); _authClient.login({ maxTimeToLive: BigInt(7 * 24 * 60 * 60 * 1000 * 1000 * 1000), @@ -24,16 +24,16 @@ export default function UserProfile() { setIsLogin(true); localStorage.setItem('authClient', JSON.stringify(_authClient)); localStorage.setItem('isLogin', JSON.stringify(true)); - }}); - _authClient + } + }); } - + useEffect(() => { const loadAuthFromLocalStorage = async () => { const storedAuthClient = localStorage.getItem('authClient'); const storedIsLogin = localStorage.getItem('isLogin'); - + if (storedAuthClient && storedIsLogin) { const _auth = JSON.parse(storedAuthClient); console.log('_auth : ', _auth); @@ -50,7 +50,7 @@ export default function UserProfile() { } } }; - + // if (storedAuthClient && storedIsLogin) { // const _auth = JSON.parse(storedAuthClient); // console.log('_auth : ', _auth); @@ -64,12 +64,12 @@ export default function UserProfile() { // setIsLogin(JSON.parse(storedIsLogin)); // } - if(isLogin) { + if (isLogin) { const userActor = new User(authClient!.getIdentity()); const userPrincipal = authClient!.getIdentity().getPrincipal(); userActor.actor.getProfile(userPrincipal).then( (result) => { - if(result.length > 0) { + if (result.length > 0) { setUserProfile(result[0]); } }, @@ -79,7 +79,7 @@ export default function UserProfile() { ); } }, [isLogin]); - + const [api, contextHolder] = notification.useNotification(); const openNotificationWithIcon = (type: NotificationType) => { @@ -91,109 +91,91 @@ export default function UserProfile() { }); }; - const handleEditProfile = () => { - if(!isLogin) { + const handleEditProfile = () => { + if (!isLogin) { openNotificationWithIcon('error') } else { setIsModalOpen(true); } }; - useEffect( () => { + useEffect(() => { }, [authClient, isLogin]); return ( -
- - + + Profile Bakcground Picture + - - - - - Profile Bakcground Picture - + + {userProfile?.name} + + {contextHolder} + + setIsModalOpen(false)} > - - - {userProfile?.name} - - {contextHolder} - - setIsModalOpen(false)} - > - - - -
- - Education : {userProfile?.education} - Company : {userProfile?.company} - -

- Biography: {userProfile?.biography} -
- - 111 Following - 222 Followers - -
- - - - - - -
- - + + +
- - -
+ + Education : {userProfile?.education} + Company : {userProfile?.company} + +

+ Biography: {userProfile?.biography} +
+ + 111 Following + 222 Followers + +
+ + + + + + + + + + + ) } diff --git a/frontend/src/routes/settings.tsx b/frontend/src/routes/settings.tsx index dc8d839..e6ba422 100644 --- a/frontend/src/routes/settings.tsx +++ b/frontend/src/routes/settings.tsx @@ -1,60 +1,45 @@ - -import { Layout, Image, Typography, Avatar, Flex, Space, Button } from 'antd'; +import {Layout, Image, Typography, Avatar, Flex, Space, Button} from 'antd'; import Sider from '../components/sider'; export default function Settings() { - return ( -
- + + - - - - - - - - - - - - - - + + + + + + + - + - - - -
- ) -} \ No newline at end of file + + + + ) +} diff --git a/frontend/src/utils/IIForIdentity.ts b/frontend/src/utils/IIForIdentity.ts new file mode 100644 index 0000000..d8fe921 --- /dev/null +++ b/frontend/src/utils/IIForIdentity.ts @@ -0,0 +1,72 @@ +import {AuthClient} from "@dfinity/auth-client"; +import {Identity} from "@dfinity/agent"; +import {Principal} from "@dfinity/principal"; + +export class IIForIdentity { + public authClient: AuthClient | undefined; + public principal: Principal | undefined; + public identity: Identity | undefined; + private isAuthClientReady: undefined | boolean = false; + + constructor() { + return this; + } + + async create() { + this.authClient = await AuthClient.create({ + idleOptions: { + idleTimeout: 1000 * 60 * 30, // set to 30 minutes + disableDefaultIdleCallback: true // disable the default reload behavior + } + }); + this.isAuthClientReady = await this.authClient?.isAuthenticated(); + } + + public setOwnerPrincipal(principal: Principal | undefined) { + this.principal = principal; + } + + //ii login + async login() { + return new Promise(async (resolve, reject) => { + this.authClient?.login({ + maxTimeToLive: BigInt(7 * 24 * 60 * 60 * 1000 * 1000 * 1000), + onSuccess: async () => { + this.identity = this.authClient?.getIdentity() as Identity | undefined; + this.principal = this.identity?.getPrincipal(); + this.isAuthClientReady = await this.authClient?.isAuthenticated(); + resolve(this.identity); + }, + onError: (err) => { + reject(err); + }, + }); + }); + } + + //II + async logout() { + await this.authClient?.logout({returnTo: "/"}); + window.location.reload() + } + + async getIdentity() { + return this.authClient?.getIdentity(); + } + + async isAuthenticated() { + return this.authClient?.isAuthenticated(); + } + + //II + async checkLogin() { + const authClient = await AuthClient.create(); + if (await authClient.isAuthenticated()) { + this.authClient = authClient; + return Promise.resolve(true); + } + return Promise.resolve(false); + } +} + +export const authClient = new IIForIdentity(); diff --git a/frontend/src/utils/common.ts b/frontend/src/utils/common.ts index 2c93174..b4b23ad 100644 --- a/frontend/src/utils/common.ts +++ b/frontend/src/utils/common.ts @@ -16,7 +16,7 @@ export function getPrincipal(auth: AuthClient) { export const checkIdentity = (auth: AuthClient | undefined) => { // console.log('checkIdentity auth : ', auth); - if(auth == undefined) return false; + if(auth === undefined) return false; let _principal = auth.getIdentity().getPrincipal(); return _principal.isAnonymous(); // let _isAuthenticated = false; @@ -29,4 +29,4 @@ export const checkIdentity = (auth: AuthClient | undefined) => { export default function getIdentity(auth: AuthClient) { return auth.getIdentity(); -} \ No newline at end of file +} diff --git a/frontend/src/utils/useAuth.tsx b/frontend/src/utils/useAuth.tsx new file mode 100644 index 0000000..e0f7b85 --- /dev/null +++ b/frontend/src/utils/useAuth.tsx @@ -0,0 +1,92 @@ +import {createContext, useContext, useEffect, useState} from "react"; +import {authClient, IIForIdentity} from "./IIForIdentity"; +import {DelegationIdentity} from "@dfinity/identity"; +import {Principal} from "@dfinity/principal"; + +export interface Props { + readonly identity: DelegationIdentity | undefined; + readonly isAuthClientReady: boolean; + readonly principal: Principal | undefined; + readonly logOut: Function | undefined; + readonly logIn: Function | undefined; + readonly isAuth: boolean; +} + +export const useProvideAuth = (authClient: IIForIdentity): Props => { + const [_identity, _setIdentity] = useState(undefined); + const [isAuthClientReady, setAuthClientReady] = useState(false); + const [principal, setPrincipal] = useState(undefined); + const [authenticated, setAuthenticated] = useState(false); + if (!isAuthClientReady) authClient.create().then(() => setAuthClientReady(true)); + + const init = async () => { + const [identity, isAuthenticated] = await Promise.all([ + authClient.getIdentity(), + authClient.isAuthenticated(), + ]) + const principal = identity?.getPrincipal() as Principal | undefined; + setPrincipal(principal); + _setIdentity(identity as DelegationIdentity | undefined); + if (isAuthenticated) { + setAuthenticated(true); + } + setAuthClientReady(true); + } + useEffect(() => { + isAuthClientReady && init().then() + }, [isAuthClientReady]); + + const logIn = async (): Promise<{ message?: string; status?: number } | undefined> => { + if (!authClient) return {message: "connect error"}; + const identity = await authClient.login(); + const principal = identity.getPrincipal(); + // const subAccountId = principalToAccountIdentifier(principal, 0); + setPrincipal(principal); + if (identity) { + _setIdentity(_identity); + setAuthenticated(true); + } else { + return {message: "connect error"}; + } + }; + + const logOut = async (): Promise => { + await authClient.logout(); + setAuthenticated(false); + }; + + + const Context: Props = { + identity: _identity, + isAuthClientReady, + principal, + logIn, + logOut, + isAuth: authenticated, + }; + return Context; +} + +const props: Props = { + identity: undefined, + isAuthClientReady: false, + principal: undefined, + logIn: undefined, + logOut: undefined, + isAuth: false, +} + +const authContext = createContext(props); + +export function ProvideAuth({children}: any) { + const auth = useProvideAuth(authClient); + return ( + + {children} + + ); +} + +export const useAuth = () => { + return useContext(authContext); +};