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;
}
#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;
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
The binaries can be obtained from the [release page](https://git.nextgraph.org/NextGraph/nextgraph-rs/releases).
You can also, [compile](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run) them from source.
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 will need to create an admin wallet on the local ngd instance, as explained in the above link.
@ -102,7 +100,7 @@ call :
```javascript
import {default as ng, init} from "nextgraphweb";
await init( location.origin, (event) => {
await init( (event) => {
// callback
// once you receive event.status == "loggedin"
// 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";
let loggedin = new Promise( async (resolve) => {
await init( location.origin, (event) => {
await init( (event) => {
// callback
// once you receive event.status == "loggedin"
// you can use the full API

@ -17,8 +17,14 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/nextgraphweb.js",
"require": "./dist/nextgraphweb.umd.cjs"
"import":{
"types": "./dist/index.d.ts",
"default": "./dist/nextgraphweb.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/nextgraphweb.umd.cjs"
}
}
},
"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
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:
@ -41,7 +41,7 @@ call :
```javascript
import {default as ng, init} from "nextgraphweb";
await init( location.origin, (event) => {
await init( (event) => {
// callback
// once you receive event.status == "loggedin"
// you can use the full API

Loading…
Cancel
Save