First commit

This commit is contained in:
srosset81
2026-07-10 11:45:54 +02:00
commit 30ea3db684
37 changed files with 10866 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
# production
dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
+10
View File
@@ -0,0 +1,10 @@
## Simple app for NextGraph
A simple app that use Refine providers compatible with NextGraph
## Quick start
```
pnpm install
pnpm run dev
```
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using refine" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<title>refine authentication example</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm run dev` or `yarn dev`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>
+53
View File
@@ -0,0 +1,53 @@
{
"name": "antd-app",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "refine build",
"dev": "refine dev",
"refine": "refine",
"start": "refine start",
"build:orm": "rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@ant-design/icons": "^5.5.1",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@ng-org/orm": "0.1.2-alpha.19",
"@ng-org/web": "0.1.2-alpha.13",
"@ng-org/refine-providers": "workspace:*",
"@refinedev/antd": "^6.0.3",
"@refinedev/cli": "^2.16.52",
"@refinedev/core": "^5.0.12",
"@refinedev/react-router": "^2.0.4",
"@uiw/react-md-editor": "^4.0.8",
"antd": "^5.23.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router": "^7.0.2"
},
"devDependencies": {
"@ng-org/shex-orm": "0.1.2-alpha.8",
"@types/node": "^20",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.8.3",
"vite": "^5.4.15"
},
"engines": {
"node": ">=20"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

+15
View File
@@ -0,0 +1,15 @@
{
"short_name": "refine authentication example",
"name": "refine authentication example",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+128
View File
@@ -0,0 +1,128 @@
import {
Refine,
Authenticated,
} from "@refinedev/core";
import {
useNotificationProvider,
ThemedLayout,
ErrorComponent,
RefineThemes,
} from "@refinedev/antd";
import {
DashboardOutlined,
} from "@ant-design/icons";
import routerProvider, {
NavigateToResource,
CatchAllNavigate,
UnsavedChangesNotifier,
DocumentTitleHandler,
} from "@refinedev/react-router";
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router";
import { App as AntdApp, ConfigProvider } from "antd";
import "@ant-design/v5-patch-for-react-19";
import "@refinedev/antd/dist/reset.css";
import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers";
import dataModels from "./shapes/data-models";
import AuthPage from "./components/AuthPage";
import { PostList, PostCreate, PostEdit, PostShow } from "../src/pages/posts";
import { DashboardPage } from "../src/pages/dashboard";
import { getSession } from "./utils/initNg";
const App: React.FC = () => (
<BrowserRouter basename={import.meta.env.MODE === 'production' ? '/nextgraph-refine-app/' : '/'}>
<ConfigProvider theme={RefineThemes.Blue}>
<AntdApp>
<Refine
// authProvider={authProvider}
dataProvider={dataProvider({ dataModels, getSession })}
liveProvider={liveProvider({ dataModels })}
routerProvider={routerProvider}
resources={[
// {
// name: "dashboard",
// list: "/",
// meta: {
// label: "Dashboard",
// icon: <DashboardOutlined />,
// },
// },
{
name: "posts",
list: "/posts",
create: "/posts/create",
show: "/posts/show/:id",
edit: "/posts/edit/:id",
meta: {
canDelete: true,
},
},
]}
notificationProvider={useNotificationProvider}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
liveMode: "auto",
disableTelemetry: true,
}}
>
<Routes>
<Route
element={
// <Authenticated
// key="authenticated-routes"
// fallback={<CatchAllNavigate to="/login" />}
// >
<ThemedLayout>
<Outlet />
</ThemedLayout>
// </Authenticated>
}
>
<Route index element={<Navigate to="/" />} />
{/* <Route index element={<DashboardPage />} /> */}
<Route path="/posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
<Route path="edit/:id" element={<PostEdit />} />
<Route path="show/:id" element={<PostShow />} />
</Route>
</Route>
{/* <Route
element={
<Authenticated key="auth-pages" fallback={<Outlet />}>
<NavigateToResource resource="posts" />
</Authenticated>
}
>
<Route path="/login" element={<AuthPage />} />
<Route path="/register" element={<AuthPage />} />
</Route> */}
<Route
element={
// <Authenticated key="catch-all">
<ThemedLayout>
<Outlet />
</ThemedLayout>
// </Authenticated>
}
>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
<UnsavedChangesNotifier />
<DocumentTitleHandler />
</Refine>
</AntdApp>
</ConfigProvider>
</BrowserRouter>
);
export default App;
@@ -0,0 +1,172 @@
import type { CSSProperties } from "react";
import {
type LoginPageProps,
type LoginFormTypes,
} from "@refinedev/core";
import {
Row,
Col,
Layout,
Card,
Form,
Input,
Button,
type CardProps,
type LayoutProps,
type FormProps,
theme,
} from "antd";
import { useLogin, useTranslate } from "@refinedev/core";
import { ThemedTitle } from "@refinedev/antd";
export const layoutStyles: CSSProperties = {};
export const containerStyles: CSSProperties = {
maxWidth: "400px",
margin: "auto",
padding: "32px",
boxShadow:
"0px 2px 4px rgba(0, 0, 0, 0.02), 0px 1px 6px -1px rgba(0, 0, 0, 0.02), 0px 1px 2px rgba(0, 0, 0, 0.03)",
};
export const headStyles: CSSProperties = {
borderBottom: 0,
padding: 0,
};
export const bodyStyles: CSSProperties = { padding: 0 };
export const titleStyles: CSSProperties = {
textAlign: "center",
marginBottom: 0,
fontSize: "24px",
lineHeight: "32px",
fontWeight: 700,
overflowWrap: "break-word",
hyphens: "manual",
textOverflow: "unset",
whiteSpace: "pre-wrap",
};
type LoginProps = LoginPageProps<LayoutProps, CardProps, FormProps>;
export const AuthPage: React.FC<LoginProps> = ({
contentProps,
wrapperProps,
renderContent,
formProps,
title,
hideForm,
mutationVariables,
}) => {
const { token } = theme.useToken();
const [form] = Form.useForm<LoginFormTypes>();
const translate = useTranslate();
const { mutate: login, isPending } = useLogin<LoginFormTypes>();
const PageTitle =
title === false ? null : (
<div
style={{
display: "flex",
justifyContent: "center",
marginBottom: "32px",
fontSize: "20px",
}}
>
{title ?? <ThemedTitle collapsed={false} />}
</div>
);
const CardContent = (
<Card
// title={CardTitle}
styles={{
header: headStyles,
body: bodyStyles,
}}
style={{
...containerStyles,
backgroundColor: token.colorBgElevated,
}}
{...(contentProps ?? {})}
>
<Form<LoginFormTypes>
layout="vertical"
form={form}
onFinish={(values) => login({ ...values, ...mutationVariables })}
requiredMark={false}
initialValues={{
brokerUrl: 'https://nextgraph.eu'
}}
{...formProps}
>
<Form.Item
name="brokerUrl"
label={translate("pages.login.fields.brokerUrl", "Broker URL")}
rules={[
{
required: true,
message: translate(
"pages.login.errors.requiredBrokerUrl",
"Broker URL is required",
),
},
{
type: "url",
message: translate(
"pages.login.errors.validBrokerUrl",
"Invalid broker URL",
),
},
]}
>
<Input
size="large"
placeholder={translate("pages.login.fields.brokerUrl", "Broker URL")}
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
size="large"
htmlType="submit"
loading={isPending}
block
>
{translate("pages.login.signin", "Sign in")}
</Button>
</Form.Item>
</Form>
</Card>
);
return (
<Layout style={layoutStyles} {...(wrapperProps ?? {})}>
<Row
justify="center"
align={hideForm ? "top" : "middle"}
style={{
padding: "16px 0",
minHeight: "100dvh",
paddingTop: hideForm ? "15dvh" : "16px",
}}
>
<Col xs={22}>
{renderContent ? (
renderContent(CardContent, PageTitle)
) : (
<>
{PageTitle}
{CardContent}
</>
)}
</Col>
</Row>
</Layout>
);
};
export default AuthPage;
+13
View File
@@ -0,0 +1,13 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
// eslint-disable-next-line
const root = createRoot(container!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
+40
View File
@@ -0,0 +1,40 @@
import { useGetIdentity, usePermissions } from "@refinedev/core";
import { Row, Col, Card, Avatar, Typography, Space } from "antd";
const { Text } = Typography;
export const DashboardPage: React.FC = () => {
const { data: identity } = useGetIdentity<{
id: string;
name: string;
avatar: string;
}>();
const permissions = usePermissions<string[], { permissions: string[] }>({
params: { permissions: ["admin"] },
});
return (
<Row gutter={20}>
<Col span={6}>
<Card
title="Identity"
style={{ height: "300px", borderRadius: "15px" }}
headStyle={{ textAlign: "center" }}
>
<Space align="center" direction="horizontal">
<Avatar size="large" src={identity?.avatar} />
<Text>{identity?.name}</Text>
</Space>
</Card>
</Col>
<Col span={6}>
<Card
title="Permissions"
style={{ height: "300px", borderRadius: "15px" }}
headStyle={{ textAlign: "center" }}
>
<Text>{permissions.data}</Text>
</Card>
</Col>
</Row>
);
};
@@ -0,0 +1,33 @@
import { Create, useForm } from "@refinedev/antd";
import { Form, Input } from "antd";
import MDEditor from "@uiw/react-md-editor";
import type { Note } from "../../shapes/orm/post.typings";
export const PostCreate = () => {
const { formProps, saveButtonProps } = useForm<Note>();
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input placeholder="Enter a post title" />
</Form.Item>
<Form.Item
label="Content"
name="content"
>
<MDEditor data-color-mode="light" />
</Form.Item>
</Form>
</Create>
);
};
@@ -0,0 +1,68 @@
import React, { useCallback, useState } from "react";
import { Edit, useForm, RefreshButton } from "@refinedev/antd";
import { useNavigation, useNotification, type LiveEvent } from "@refinedev/core";
import { Alert, Form, Input } from "antd";
import MDEditor from "@uiw/react-md-editor";
import type { Note } from "../../shapes/orm/post.typings";
export const PostEdit = () => {
const [deprecated, setDeprecated] = useState<boolean>(false);
const { list } = useNavigation();
const { open } = useNotification();
const onLiveEvent = useCallback((event: LiveEvent) => {
console.log('onLiveEvent', event);
if (event.type === "deleted") {
open?.({
type: "error",
message: "This post has been deleted",
});
list("posts");
} else if (event.type === "updated") {
setDeprecated(true);
}
}, [open, list]);
const { formProps, saveButtonProps, query: queryResult } = useForm<Note>({
liveMode: 'manual',
onLiveEvent,
});
const handleRefresh = () => {
queryResult?.refetch();
setDeprecated(false);
};
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
{deprecated && (
<Alert
message="This post has been updated. Refresh to see changes."
type="warning"
style={{ marginBottom: 20 }}
action={<RefreshButton size="small" onClick={handleRefresh} />}
/>
)}
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Content"
name="content"
>
<MDEditor data-color-mode="light" />
</Form.Item>
</Form>
</Edit>
);
};
@@ -0,0 +1,4 @@
export * from "./list";
export * from "./create";
export * from "./edit";
export * from "./show";
@@ -0,0 +1,49 @@
import {
List,
useTable,
EditButton,
ShowButton,
DeleteButton,
CreateButton,
} from "@refinedev/antd";
import { useNavigation } from "@refinedev/core";
import { Table, Space } from "antd";
import type { Note } from "../../shapes/orm/post.typings";
const shortenNuri = (nuri: string) => {
return nuri?.split(':')[3]?.slice(0, 7);
};
export const PostList = () => {
const { tableProps } = useTable<Note>();
const { show } = useNavigation();
return (
<List headerButtons={<CreateButton />}>
<Table
{...tableProps}
rowKey="@graph"
onRow={(record) => ({
onClick: () => show("posts", record["@graph"]),
style: { cursor: "pointer" },
})}
>
<Table.Column dataIndex="@graph" title="ID" render={shortenNuri} />
<Table.Column dataIndex="title" title="Title" />
<Table.Column dataIndex="content" title="Content" />
<Table.Column<Note>
title="Actions"
dataIndex="actions"
align="right"
render={(_, record) => (
<Space onClick={(e) => e.stopPropagation()}>
<EditButton hideText size="small" recordItemId={record['@graph']} />
<ShowButton hideText size="small" recordItemId={record['@graph']} />
<DeleteButton hideText size="small" recordItemId={record['@graph']} />
</Space>
)}
/>
</Table>
</List>
);
};
@@ -0,0 +1,37 @@
import { useCallback } from "react";
import { useShow, useNavigation, useNotification, type LiveEvent } from "@refinedev/core";
import { Show, MarkdownField } from "@refinedev/antd";
import { Typography } from "antd";
import type { Note } from "../../shapes/orm/post.typings";
const { Title, Text } = Typography;
export const PostShow = () => {
const { list } = useNavigation();
const { open } = useNotification();
const onLiveEvent = useCallback((event: LiveEvent) => {
if (event.type === "deleted") {
open?.({
type: "error",
message: "This post has been deleted",
});
list("posts");
}
}, [open, list]);
const { query: queryResult } = useShow<Note>({ liveMode: "auto", onLiveEvent });
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>Id</Title>
<Text>{record?.['@graph']}</Text>
<Title level={5}>Title</Title>
<Text>{record?.title}</Text>
<Title level={5}>Content</Title>
<MarkdownField value={record?.content} />
</Show>
);
};
@@ -0,0 +1,10 @@
import { DataModels } from "@ng-org/refine-providers";
import { NoteShapeType } from "./orm/post.shapeTypes";
const dataModels : DataModels = {
posts: {
shapeType: NoteShapeType
}
}
export default dataModels;
@@ -0,0 +1,48 @@
import type { Schema } from "@ng-org/shex-orm";
/**
* =============================================================================
* postSchema: Schema for post
* =============================================================================
*/
export const postSchema = {
"https://www.w3.org/ns/activitystreams#Note": {
iri: "https://www.w3.org/ns/activitystreams#Note",
predicates: [
{
dataTypes: [
{
valType: "iri",
literals: ["https://www.w3.org/ns/activitystreams#Note"],
},
],
maxCardinality: 1,
minCardinality: 1,
iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
readablePredicate: "@type",
},
{
dataTypes: [
{
valType: "string",
},
],
maxCardinality: 1,
minCardinality: 1,
iri: "https://www.w3.org/ns/activitystreams#title",
readablePredicate: "title",
},
{
dataTypes: [
{
valType: "string",
},
],
maxCardinality: 1,
minCardinality: 0,
iri: "https://www.w3.org/ns/activitystreams#content",
readablePredicate: "content",
},
],
},
} as const satisfies Schema;
@@ -0,0 +1,9 @@
import type { ShapeType } from "@ng-org/shex-orm";
import { postSchema } from "./post.schema";
import type { Note } from "./post.typings";
// ShapeTypes for post
export const NoteShapeType = {
schema: postSchema,
shape: "https://www.w3.org/ns/activitystreams#Note",
} as const satisfies ShapeType<Note>;
@@ -0,0 +1,37 @@
export type IRI = string;
/**
* =============================================================================
* Typescript Typings for post
* =============================================================================
*/
/**
* Note Type
*/
export interface Note {
/**
* The graph NURI.
*/
readonly "@graph": IRI;
/**
* The subject IRI.
*/
readonly "@id": IRI;
/**
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
*/
"@type": "https://www.w3.org/ns/activitystreams#Note";
/**
* The title of the post
*
* Original IRI: https://www.w3.org/ns/activitystreams#title
*/
title: string;
/**
* The content of the post
*
* Original IRI: https://www.w3.org/ns/activitystreams#content
*/
content?: string;
}
@@ -0,0 +1,11 @@
PREFIX as: <https://www.w3.org/ns/activitystreams#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
as:Note {
a [as:Note] ;
as:title xsd:string
// rdfs:comment "The title of the post" ;
as:content xsd:string ?
// rdfs:comment "The content of the post" ;
}
+17
View File
@@ -0,0 +1,17 @@
import { ng, init, Session } from "@ng-org/web";
import { initNg } from "@ng-org/orm";
export let session: Session | undefined;
init(
async (event: any) => {
session = event.session;
// The ORM needs to have access to ng,
// the interface to the engine running in WASM.
initNg(ng, session!);
},
true,
[],
);
export const getSession = () => session;
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
+6
View File
@@ -0,0 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});