parent
5c98e8f532
commit
a81692cd80
@ -0,0 +1,24 @@ |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
lerna-debug.log* |
||||
|
||||
node_modules |
||||
dist |
||||
dist-ssr |
||||
*.local |
||||
|
||||
# Editor directories and files |
||||
.vscode/* |
||||
!.vscode/extensions.json |
||||
.idea |
||||
.DS_Store |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.sw? |
@ -0,0 +1,75 @@ |
||||
# example-webapp-react |
||||
|
||||
Example of a Web app made with NextGraph, using React and LDO, and Vite |
||||
|
||||
## NextGraph |
||||
|
||||
> NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. |
||||
> |
||||
> This open source ecosystem provides solutions for end-users (a platform) and software developers (a framework), wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. |
||||
> |
||||
> More info here [https://nextgraph.org](https://nextgraph.org) |
||||
|
||||
## For developing against a public Broker |
||||
|
||||
``` |
||||
npm install |
||||
npm run dev |
||||
``` |
||||
|
||||
You will have to use a Wallet that was created on one of our public Broker Service Providers (nextgraph.eu by example). |
||||
|
||||
## For developing locally |
||||
|
||||
you need to have a running local ngd server. See those [instructions first](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run). |
||||
|
||||
Then compile the nextgraphweb package in dev mode: |
||||
|
||||
``` |
||||
pnpm run -C ../../helpers/nextgraphweb builddev |
||||
``` |
||||
|
||||
``` |
||||
npm install |
||||
npm link ../../helpers/nextgraphweb |
||||
npm run dev |
||||
``` |
||||
|
||||
Due to the way `npm link` works, you will have to run this command again, after each time you use `npm install`. |
||||
|
||||
Open this URL in browser : [http://localhost:5173](http://localhost:5173) |
||||
|
||||
See the example code in [src/main.tsx](./src/main.tsx) |
||||
|
||||
## For usage in your project |
||||
|
||||
call : |
||||
|
||||
```javascript |
||||
import {default as ng, init} from "nextgraphweb"; |
||||
|
||||
await init( location.origin, (event) => { |
||||
// callback |
||||
// once you receive event.status == "loggedin" |
||||
// you can use the full API |
||||
} |
||||
, true // singleton: boolean (will your app create many docs in the system, or should it be launched as a unique instance) |
||||
, []); //list of AccessRequests (for now, leave this empty) |
||||
|
||||
await ng.login(); // this will return false at the first attempt. but it will open the wallet login page so the user can login. |
||||
// if you call it again later once the user has logged in already, it will return true, and nothing more will happen |
||||
``` |
||||
|
||||
## License |
||||
|
||||
Licensed under either of |
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) |
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) |
||||
at your option. |
||||
|
||||
`SPDX-License-Identifier: Apache-2.0 OR MIT` |
||||
|
||||
--- |
||||
|
||||
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively. |
@ -0,0 +1,28 @@ |
||||
import js from '@eslint/js' |
||||
import globals from 'globals' |
||||
import reactHooks from 'eslint-plugin-react-hooks' |
||||
import reactRefresh from 'eslint-plugin-react-refresh' |
||||
import tseslint from 'typescript-eslint' |
||||
|
||||
export default tseslint.config( |
||||
{ ignores: ['dist'] }, |
||||
{ |
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended], |
||||
files: ['**/*.{ts,tsx}'], |
||||
languageOptions: { |
||||
ecmaVersion: 2020, |
||||
globals: globals.browser, |
||||
}, |
||||
plugins: { |
||||
'react-hooks': reactHooks, |
||||
'react-refresh': reactRefresh, |
||||
}, |
||||
rules: { |
||||
...reactHooks.configs.recommended.rules, |
||||
'react-refresh/only-export-components': [ |
||||
'warn', |
||||
{ allowConstantExport: true }, |
||||
], |
||||
}, |
||||
}, |
||||
) |
@ -0,0 +1,12 @@ |
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>NextGraph WebApp React example</title> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
<script type="module" src="/src/main.tsx"></script> |
||||
</body> |
||||
</html> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@ |
||||
{ |
||||
"name": "example-webapp-react", |
||||
"private": true, |
||||
"version": "0.0.0", |
||||
"type": "module", |
||||
"scripts": { |
||||
"dev": "vite", |
||||
"build": "tsc -b && vite build", |
||||
"lint": "eslint .", |
||||
"preview": "vite preview" |
||||
}, |
||||
"dependencies": { |
||||
"@ldo/connected-nextgraph": "^1.0.0-alpha.7", |
||||
"@ldo/react": "^1.0.0-alpha.3", |
||||
"nextgraphweb": "^0.1.1-alpha.1", |
||||
"react": "^19.0.0", |
||||
"react-dom": "^19.0.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@eslint/js": "^9.22.0", |
||||
"@types/react": "^19.0.10", |
||||
"@types/react-dom": "^19.0.4", |
||||
"@vitejs/plugin-react": "^4.3.4", |
||||
"autoprefixer": "^10.4.21", |
||||
"eslint": "^9.22.0", |
||||
"eslint-plugin-react-hooks": "^5.2.0", |
||||
"eslint-plugin-react-refresh": "^0.4.19", |
||||
"globals": "^16.0.0", |
||||
"postcss": "^8.5.3", |
||||
"tailwindcss": "^3.4.17", |
||||
"typescript": "~5.7.2", |
||||
"typescript-eslint": "^8.26.1", |
||||
"vite": "^6.3.1" |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
export default { |
||||
plugins: { |
||||
tailwindcss: {}, |
||||
autoprefixer: {}, |
||||
}, |
||||
} |
@ -0,0 +1 @@ |
||||
|
@ -0,0 +1,33 @@ |
||||
import { useState } from 'react' |
||||
import React, { FunctionComponent } from 'react'; |
||||
import { Header } from './Header'; |
||||
import { Contact } from './Contact'; |
||||
import { BrowserNGLdoProvider } from './reactMethods'; |
||||
|
||||
import './App.css' |
||||
|
||||
import "../../../common/src/styles.css"; |
||||
|
||||
// function App() {
|
||||
// const [count, setCount] = useState(0)
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||
// <h1 className="text-4xl font-bold text-blue-600">Hello, Tailwind!</h1>
|
||||
// <p className="mt-4 text-gray-700">Tailwind CSS is working in Vite!</p>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
const App: FunctionComponent = () => { |
||||
return ( |
||||
<div className="App"> |
||||
<BrowserNGLdoProvider> |
||||
<Header /> |
||||
<Contact /> |
||||
</BrowserNGLdoProvider> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
export default App |
@ -0,0 +1,12 @@ |
||||
import { FunctionComponent } from "react"; |
||||
import { useNextGraphAuth } from "./reactMethods"; |
||||
|
||||
export const Contact: FunctionComponent = () => { |
||||
const { session } = useNextGraphAuth(); |
||||
if (!session.sessionId) return <></>; |
||||
return ( |
||||
<div> |
||||
<p>Contact</p> |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,43 @@ |
||||
import { FunctionComponent } from "react"; |
||||
import { useNextGraphAuth } from "./reactMethods"; |
||||
|
||||
|
||||
export const Header: FunctionComponent = () => { |
||||
|
||||
const { session, login, logout } = useNextGraphAuth(); |
||||
|
||||
return ( |
||||
<div className="full-layout"> |
||||
|
||||
{session.sessionId ? ( |
||||
// If the session is logged in
|
||||
<div className="p-1 text-center fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||
You are logged in. <span className="font-bold clickable" onClick={logout}> Log out</span> |
||||
</div> |
||||
) : ( |
||||
// If the session is not logged in
|
||||
<> |
||||
<h1 className="text-2xl text-center mb-10">Welcome to your contact manager</h1> |
||||
<div className="text-center text-xl p-1 text-white fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||
Please <span className="font-bold clickable" onClick={login}> Log in</span> |
||||
</div> |
||||
|
||||
<div className="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-800"> |
||||
|
||||
<svg className="mt-10 h-16 w-16 mx-auto" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"></path> |
||||
</svg> |
||||
|
||||
<button |
||||
onClick={login} |
||||
onKeyUp={login} |
||||
className="select-none ml-0 mt-2 mb-10 text-white bg-blue-800 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||
> |
||||
Please Log in |
||||
</button> |
||||
</div> |
||||
</> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,32 @@ |
||||
import { FormEvent, FunctionComponent, useCallback, useState } from "react"; |
||||
|
||||
export const MakePost: FunctionComponent = () => { |
||||
const [name, setName] = useState(""); |
||||
const [email, setEmail] = useState(""); |
||||
|
||||
const onSubmit = useCallback( |
||||
async (e: FormEvent<HTMLFormElement>) => { |
||||
e.preventDefault(); |
||||
|
||||
}, |
||||
[name, email] |
||||
); |
||||
|
||||
return ( |
||||
<form onSubmit={onSubmit}> |
||||
<input |
||||
type="text" |
||||
placeholder="Enter name" |
||||
value={name} |
||||
onChange={(e) => setName(e.target.value)} |
||||
/> |
||||
<input |
||||
type="text" |
||||
placeholder="Enter email address" |
||||
value={email} |
||||
onChange={(e) => setEmail(e.target.value)} |
||||
/> |
||||
<input type="submit" value="Post" /> |
||||
</form> |
||||
); |
||||
}; |
@ -0,0 +1,20 @@ |
||||
import { createContext, useContext } from "react"; |
||||
|
||||
/** |
||||
* Functions for authenticating with NextGraph |
||||
*/ |
||||
export interface NGWalletAuthFunctions { |
||||
login: () => Promise<void>; |
||||
logout: () => Promise<void>; |
||||
session: unknown; |
||||
ranInitialAuthCheck: boolean; |
||||
} |
||||
|
||||
// There is no initial value for this context. It will be given in the provider
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
export const NextGraphAuthContext = createContext<NGWalletAuthFunctions>(undefined); |
||||
|
||||
export function useNextGraphAuth(): NGWalletAuthFunctions { |
||||
return useContext(NextGraphAuthContext); |
||||
} |
@ -0,0 +1,108 @@ |
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; |
||||
import type { FunctionComponent, PropsWithChildren } from "react"; |
||||
import { NextGraphAuthContext, useNextGraphAuth } from "./NextGraphAuthContext"; |
||||
|
||||
import {default as ng, init} from "nextgraphweb"; |
||||
|
||||
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; |
||||
import type { NextGraphConnectedPlugin, NextGraphConnectedContext } from "@ldo/connected-nextgraph"; |
||||
|
||||
/** |
||||
* Creates special react methods specific to the NextGraph Auth |
||||
* @param dataset the connectedLdoDataset with a nextGraphConnectedPlugin |
||||
* @returns { BrowserNGLdoProvider, useNextGraphAuth } |
||||
*/ |
||||
export function createBrowserNGReactMethods( |
||||
dataset: ConnectedLdoDataset<(NextGraphConnectedPlugin | ConnectedPlugin)[]>, |
||||
) { |
||||
|
||||
const BrowserNGLdoProvider: FunctionComponent<PropsWithChildren> = ({ |
||||
children, |
||||
}) => { |
||||
const [session, setSession] = useState<NextGraphConnectedContext>( |
||||
{ |
||||
ng: undefined, |
||||
} |
||||
); |
||||
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); |
||||
|
||||
const runInitialAuthCheck = useCallback(async () => { |
||||
console.log("runInitialAuthCheck called", ranInitialAuthCheck) |
||||
if (ranInitialAuthCheck) return; |
||||
|
||||
console.log("init called"); |
||||
setRanInitialAuthCheck(true); |
||||
// TODO: export the types for the session object coming from NG.
|
||||
await init( (event: { status: string; session: { session_id: unknown; protected_store_id: unknown; private_store_id: unknown; public_store_id: unknown; }; }) => { |
||||
console.log("called back in react", event) |
||||
|
||||
// callback
|
||||
// once you receive event.status == "loggedin"
|
||||
// you can use the full API
|
||||
if (event.status == "loggedin") { |
||||
setSession({
|
||||
ng,
|
||||
sessionId: event.session.session_id as string, //FIXME: sessionId should be a Number.
|
||||
protectedStoreId: event.session.protected_store_id as string, |
||||
privateStoreId: event.session.private_store_id as string, |
||||
publicStoreId: event.session.public_store_id as string |
||||
}); // TODO: add event.session.user too
|
||||
|
||||
dataset.setContext("nextgraph", { |
||||
ng, |
||||
sessionId: event.session.session_id as string |
||||
}); |
||||
} |
||||
else if (event.status == "cancelled" || event.status == "error" || event.status == "loggedout") { |
||||
setSession({ ng: undefined }); |
||||
dataset.setContext("nextgraph", { |
||||
ng: undefined, |
||||
}); |
||||
} |
||||
} |
||||
, true // singleton: boolean (will your app create many docs in the system, or should it be launched as a unique instance)
|
||||
, []); //list of AccessRequests (for now, leave this empty)
|
||||
|
||||
}, []); |
||||
|
||||
|
||||
const login = useCallback( |
||||
async () => { |
||||
await ng.login(); |
||||
}, |
||||
[], |
||||
); |
||||
|
||||
const logout = useCallback(async () => { |
||||
await ng.logout(); |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
runInitialAuthCheck(); |
||||
}, []); |
||||
|
||||
const nextGraphAuthFunctions = useMemo( |
||||
() => ({ |
||||
login, |
||||
logout, |
||||
session, |
||||
}), |
||||
[ |
||||
login, |
||||
logout, |
||||
session, |
||||
], |
||||
); |
||||
|
||||
return ( |
||||
<NextGraphAuthContext.Provider value={nextGraphAuthFunctions}> |
||||
{children} |
||||
</NextGraphAuthContext.Provider> |
||||
); |
||||
}; |
||||
|
||||
return { |
||||
BrowserNGLdoProvider, |
||||
useNextGraphAuth: useNextGraphAuth |
||||
}; |
||||
}; |
@ -0,0 +1,3 @@ |
||||
@tailwind base; |
||||
@tailwind components; |
||||
@tailwind utilities; |
@ -0,0 +1,10 @@ |
||||
import { StrictMode } from 'react' |
||||
import { createRoot } from 'react-dom/client' |
||||
import './index.css' |
||||
import App from './App.tsx' |
||||
|
||||
createRoot(document.getElementById('app')!).render( |
||||
// <StrictMode>
|
||||
<App /> |
||||
// </StrictMode>,
|
||||
) |
@ -0,0 +1,19 @@ |
||||
import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph"; |
||||
import { createLdoReactMethods } from "@ldo/react"; |
||||
import { createBrowserNGReactMethods } from "./createBrowserNGReactMethods"; |
||||
|
||||
|
||||
export const { |
||||
dataset, |
||||
useLdo, |
||||
useMatchObject, |
||||
useMatchSubject, |
||||
useResource, |
||||
useSubject, |
||||
useSubscribeToResource, |
||||
} = createLdoReactMethods([nextGraphConnectedPlugin]); |
||||
|
||||
const methods = createBrowserNGReactMethods(dataset); |
||||
|
||||
export const { BrowserNGLdoProvider, useNextGraphAuth } = methods; |
||||
|
@ -0,0 +1 @@ |
||||
/// <reference types="vite/client" />
|
@ -0,0 +1,12 @@ |
||||
/** @type {import('tailwindcss').Config} */ |
||||
export default { |
||||
content: [ |
||||
"./index.html", |
||||
"./src/**/*.{js,jsx,ts,tsx}" |
||||
], |
||||
theme: { |
||||
extend: {}, |
||||
}, |
||||
plugins: [], |
||||
} |
||||
|
@ -0,0 +1,26 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||
"target": "ES2020", |
||||
"useDefineForClassFields": true, |
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"], |
||||
"module": "ESNext", |
||||
"skipLibCheck": true, |
||||
|
||||
/* Bundler mode */ |
||||
"moduleResolution": "bundler", |
||||
"allowImportingTsExtensions": true, |
||||
"isolatedModules": true, |
||||
"moduleDetection": "force", |
||||
"noEmit": true, |
||||
"jsx": "react-jsx", |
||||
|
||||
/* Linting */ |
||||
"strict": true, |
||||
"noUnusedLocals": true, |
||||
"noUnusedParameters": true, |
||||
"noFallthroughCasesInSwitch": true, |
||||
"noUncheckedSideEffectImports": true |
||||
}, |
||||
"include": ["src"] |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"files": [], |
||||
"references": [ |
||||
{ "path": "./tsconfig.app.json" }, |
||||
{ "path": "./tsconfig.node.json" } |
||||
] |
||||
} |
@ -0,0 +1,24 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||
"target": "ES2022", |
||||
"lib": ["ES2023"], |
||||
"module": "ESNext", |
||||
"skipLibCheck": true, |
||||
|
||||
/* Bundler mode */ |
||||
"moduleResolution": "bundler", |
||||
"allowImportingTsExtensions": true, |
||||
"isolatedModules": true, |
||||
"moduleDetection": "force", |
||||
"noEmit": true, |
||||
|
||||
/* Linting */ |
||||
"strict": true, |
||||
"noUnusedLocals": true, |
||||
"noUnusedParameters": true, |
||||
"noFallthroughCasesInSwitch": true, |
||||
"noUncheckedSideEffectImports": true |
||||
}, |
||||
"include": ["vite.config.ts"] |
||||
} |
@ -0,0 +1,7 @@ |
||||
import { defineConfig } from 'vite' |
||||
import react from '@vitejs/plugin-react' |
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({ |
||||
plugins: [react()], |
||||
}) |
Loading…
Reference in new issue