example webapp with React

master
Niko PLP 2 weeks ago
parent 5c98e8f532
commit a81692cd80
  1. 1
      helpers/net-auth/index.html
  2. 8
      helpers/nextgraphweb/README.md
  3. 10
      helpers/nextgraphweb/package.json
  4. 24
      ng-sdk-js/example-webapp-react/.gitignore
  5. 75
      ng-sdk-js/example-webapp-react/README.md
  6. 28
      ng-sdk-js/example-webapp-react/eslint.config.js
  7. 12
      ng-sdk-js/example-webapp-react/index.html
  8. 4973
      ng-sdk-js/example-webapp-react/package-lock.json
  9. 35
      ng-sdk-js/example-webapp-react/package.json
  10. 6
      ng-sdk-js/example-webapp-react/postcss.config.js
  11. 1
      ng-sdk-js/example-webapp-react/src/App.css
  12. 33
      ng-sdk-js/example-webapp-react/src/App.tsx
  13. 12
      ng-sdk-js/example-webapp-react/src/Contact.tsx
  14. 43
      ng-sdk-js/example-webapp-react/src/Header.tsx
  15. 32
      ng-sdk-js/example-webapp-react/src/MakeContact.tsx
  16. 20
      ng-sdk-js/example-webapp-react/src/NextGraphAuthContext.ts
  17. 108
      ng-sdk-js/example-webapp-react/src/createBrowserNGReactMethods.tsx
  18. 3
      ng-sdk-js/example-webapp-react/src/index.css
  19. 10
      ng-sdk-js/example-webapp-react/src/main.tsx
  20. 19
      ng-sdk-js/example-webapp-react/src/reactMethods.ts
  21. 1
      ng-sdk-js/example-webapp-react/src/vite-env.d.ts
  22. 12
      ng-sdk-js/example-webapp-react/tailwind.config.js
  23. 26
      ng-sdk-js/example-webapp-react/tsconfig.app.json
  24. 7
      ng-sdk-js/example-webapp-react/tsconfig.json
  25. 24
      ng-sdk-js/example-webapp-react/tsconfig.node.json
  26. 7
      ng-sdk-js/example-webapp-react/vite.config.ts
  27. 4
      ng-sdk-js/example-webapp-vite/README.md

@ -32,6 +32,7 @@
visibility: visible; visibility: visible;
} }
#banner { #banner {
padding-right: 36px !important;
width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem; width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem;
line-height: 1.75rem; overflow-wrap: break-word; line-height: 1.75rem; overflow-wrap: break-word;
} }

@ -62,9 +62,7 @@ Here is the format of the config object to be supplied in the calls to `init_hea
## Local development ## Local development
The binaries can be obtained from the [release page](https://git.nextgraph.org/NextGraph/nextgraph-rs/releases). you need to have a running local ngd server and a local ng-app frontend too. See those [instructions first](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run).
You can also, [compile](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run) them from source.
You will need to create an admin wallet on the local ngd instance, as explained in the above link. You will need to create an admin wallet on the local ngd instance, as explained in the above link.
@ -102,7 +100,7 @@ call :
```javascript ```javascript
import {default as ng, init} from "nextgraphweb"; import {default as ng, init} from "nextgraphweb";
await init( location.origin, (event) => { await init( (event) => {
// callback // callback
// once you receive event.status == "loggedin" // once you receive event.status == "loggedin"
// you can use the full API // you can use the full API
@ -120,7 +118,7 @@ You can alternatively wrap the callback inside a Promise in order to wait for th
import {default as ng, init} from "nextgraphweb"; import {default as ng, init} from "nextgraphweb";
let loggedin = new Promise( async (resolve) => { let loggedin = new Promise( async (resolve) => {
await init( location.origin, (event) => { await init( (event) => {
// callback // callback
// once you receive event.status == "loggedin" // once you receive event.status == "loggedin"
// you can use the full API // you can use the full API

@ -17,8 +17,14 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"import": "./dist/nextgraphweb.js", "import":{
"require": "./dist/nextgraphweb.umd.cjs" "types": "./dist/index.d.ts",
"default": "./dist/nextgraphweb.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/nextgraphweb.umd.cjs"
}
} }
}, },
"scripts": { "scripts": {

@ -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,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()],
})

@ -12,7 +12,7 @@ Example of a Web app made with NextGraph, using Vite
## For developing locally ## 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). you need to have a running local ngd server and a local ng-app frontend too. 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: Then compile the nextgraphweb package in dev mode:
@ -41,7 +41,7 @@ call :
```javascript ```javascript
import {default as ng, init} from "nextgraphweb"; import {default as ng, init} from "nextgraphweb";
await init( location.origin, (event) => { await init( (event) => {
// callback // callback
// once you receive event.status == "loggedin" // once you receive event.status == "loggedin"
// you can use the full API // you can use the full API

Loading…
Cancel
Save