Skip to content

Commit dd27d82

Browse files
committed
Initial commit
1 parent de66678 commit dd27d82

18 files changed

Lines changed: 878 additions & 1 deletion

Client-side/package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "restrict-editing",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@syncfusion/ej2-react-documenteditor": "^29.2.5",
7+
"@testing-library/dom": "^10.4.0",
8+
"@testing-library/jest-dom": "^6.6.3",
9+
"@testing-library/react": "^16.3.0",
10+
"@testing-library/user-event": "^13.5.0",
11+
"react": "^19.1.0",
12+
"react-dom": "^19.1.0",
13+
"react-scripts": "5.0.1",
14+
"web-vitals": "^2.1.4"
15+
},
16+
"scripts": {
17+
"start": "react-scripts start",
18+
"build": "react-scripts build",
19+
"test": "react-scripts test",
20+
"eject": "react-scripts eject"
21+
},
22+
"eslintConfig": {
23+
"extends": [
24+
"react-app",
25+
"react-app/jest"
26+
]
27+
},
28+
"browserslist": {
29+
"production": [
30+
">0.2%",
31+
"not dead",
32+
"not op_mini all"
33+
],
34+
"development": [
35+
"last 1 chrome version",
36+
"last 1 firefox version",
37+
"last 1 safari version"
38+
]
39+
}
40+
}
26.6 KB
Binary file not shown.

Client-side/public/index.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<meta
9+
name="description"
10+
content="Web site created using create-react-app"
11+
/>
12+
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13+
<!--
14+
manifest.json provides metadata used when your web app is installed on a
15+
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16+
-->
17+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18+
<!--
19+
Notice the use of %PUBLIC_URL% in the tags above.
20+
It will be replaced with the URL of the `public` folder during the build.
21+
Only files inside the `public` folder can be referenced from the HTML.
22+
23+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24+
work correctly both with client-side routing and a non-root public URL.
25+
Learn how to configure a non-root public URL by running `npm run build`.
26+
-->
27+
<title>Role based Customized Toolbar Sample</title>
28+
</head>
29+
<body>
30+
<noscript>You need to enable JavaScript to run this app.</noscript>
31+
<div id="root"></div>
32+
<!--
33+
This HTML file is a template.
34+
If you open it directly in the browser, you will see an empty page.
35+
36+
You can add webfonts, meta tags, or analytics to this file.
37+
The build step will place the bundled scripts into the <body> tag.
38+
39+
To begin the development, run `npm start` or `yarn start`.
40+
To create a production bundle, use `npm run build` or `yarn build`.
41+
-->
42+
</body>
43+
</html>

Client-side/src/Authentication.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Import necessary hooks and components
2+
import { useState, useEffect } from "react";
3+
import { registerLicense } from '@syncfusion/ej2-base';
4+
import DocumentEditorMain from "./DocumentEditor";
5+
6+
// Register the Syncfusion license key
7+
registerLicense("Ngo9BigBOggjHTQxAR8/V1NNaF5cXGhIfEx1RHxQdld5ZFRHallYTnNWUj0eQnxTdEBjWH1ecXxWRWVbUExyVklfag==");
8+
9+
const hostURL = "https://localhost:44310/api/authentication";
10+
11+
// Authenticating the users with the credentials like Email ID and password. Registering new users.
12+
function Authentication() {
13+
// State hooks for managing authentication
14+
const [user, setUser] = useState(null);
15+
const [isRegister, setIsRegister] = useState(false);
16+
const [message, setMessage] = useState("");
17+
const [username, setUsername] = useState("");
18+
19+
// Effect to load user from localStorage on component mount
20+
useEffect(() => {
21+
const saved = localStorage.getItem("user");
22+
if (saved) {
23+
setUser(JSON.parse(saved));
24+
}
25+
}, []);
26+
27+
// Logout user and clear state
28+
const handleLogout = () => {
29+
localStorage.removeItem("user");
30+
setUser(null);
31+
};
32+
33+
// Validate email format using a regular expression
34+
const validateEmail = (email) => /\S+@\S+\.\S+/.test(email);
35+
36+
// Handle login form submission
37+
const handleLogin = async (event) => {
38+
event.preventDefault();
39+
setMessage("");
40+
41+
const form = event.currentTarget;
42+
const email = form.elements.email.value.trim();
43+
const password = form.elements.password.value.trim();
44+
45+
if (!validateEmail(email)) {
46+
setMessage("❌ Please enter a valid email.");
47+
return;
48+
}
49+
50+
try {
51+
// Make a POST request to the login API
52+
const response = await fetch(`${hostURL}/login`, {
53+
method: "POST",
54+
headers: { "Content-Type": "application/json" },
55+
body: JSON.stringify({ email, password }),
56+
});
57+
58+
if (response.ok) {
59+
const userData = await response.json();
60+
localStorage.setItem("user", JSON.stringify(userData));
61+
setUser(userData); // Update user state directly
62+
} else {
63+
setMessage("❌ Invalid email or password.");
64+
}
65+
} catch (error) {
66+
setMessage("⚠️ Server error during login.");
67+
}
68+
};
69+
70+
// Handle registration form submission
71+
const handleRegister = async (e) => {
72+
e.preventDefault();
73+
setMessage("");
74+
75+
const form = e.currentTarget;
76+
const username = form.elements.username.value;
77+
const email = form.elements.email.value;
78+
const password = form.elements.password.value;
79+
80+
if (!validateEmail(email)) {
81+
setMessage("❌ Please enter a valid email.");
82+
return;
83+
}
84+
85+
try {
86+
// Make a POST request to the registration API
87+
const register = await fetch(`${hostURL}/register`, {
88+
method: "POST",
89+
headers: { "Content-Type": "application/json" },
90+
body: JSON.stringify({ username, email, password }),
91+
});
92+
93+
const data = await register.json();
94+
if (register.ok) {
95+
setMessage("✅ Registration successful. You can now log in.");
96+
setIsRegister(false);
97+
setUsername("");
98+
} else {
99+
setMessage(`❌ ${data.message || "Registration failed"}`);
100+
}
101+
} catch {
102+
setMessage("⚠️ Server error during registration.");
103+
}
104+
};
105+
106+
// Render authentication form if not logged in
107+
const renderAuthForm = () => {
108+
return (
109+
<div className="auth-container">
110+
<h2 className="auth-title">{isRegister ? "Register" : "Login"}</h2>
111+
<form onSubmit={isRegister ? handleRegister : handleLogin}>
112+
{isRegister && (
113+
<>
114+
<label className="auth-label" htmlFor="username">Username:</label>
115+
<input
116+
id="username"
117+
type="text"
118+
name="username"
119+
value={username}
120+
onChange={(e) => setUsername(e.target.value)}
121+
required
122+
className="auth-input"
123+
placeholder="Your username"
124+
/>
125+
</>
126+
)}
127+
<label className="auth-label" htmlFor="email">Email:</label>
128+
<input
129+
id="email"
130+
type="email"
131+
name="email"
132+
required
133+
className="auth-input"
134+
placeholder="you@example.com"
135+
/>
136+
<label className="auth-label" htmlFor="password">Password:</label>
137+
<input
138+
id="password"
139+
type="password"
140+
name="password"
141+
required
142+
className="auth-input"
143+
placeholder="Your password"
144+
/>
145+
<button
146+
type="submit"
147+
className="auth-button primary"
148+
>
149+
{isRegister ? "Register" : "Login"}
150+
</button>
151+
</form>
152+
<button
153+
className="auth-button secondary"
154+
onClick={() => {
155+
setIsRegister(!isRegister);
156+
setMessage("");
157+
setUsername("");
158+
}}
159+
style={{ marginTop: 12, cursor: "pointer" }}
160+
>
161+
{isRegister ? "Already have an account? Log in" : "Don't have an account? Register"}
162+
</button>
163+
{message && <p className="auth-message" style={{ marginTop: 12 }}>{message}</p>}
164+
</div>
165+
);
166+
};
167+
168+
// Render DocumentEditorMain if user is authenticated, otherwise render auth form
169+
return user ? (
170+
<DocumentEditorMain user={user} onLogout={handleLogout} />
171+
) : (
172+
renderAuthForm()
173+
);
174+
}
175+
176+
export default Authentication;

Client-side/src/DocumentEditor.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useEffect, useRef } from "react";
2+
import { DocumentEditorContainerComponent, Toolbar as DocumentEditorToolbar } from "@syncfusion/ej2-react-documenteditor";
3+
import TitleBar from "./Titlebar";
4+
5+
// Inject the toolbar to the document editor component
6+
DocumentEditorContainerComponent.Inject(DocumentEditorToolbar);
7+
8+
function DocumentEditor({ user, onLogout }) {
9+
const defaultDocument = '';
10+
// Configure toolbar items based on user role
11+
const toolbarConfig = {
12+
Lawyer: ["New", "Open", "Separator", "Undo", "Redo", "Separator", "Image", "Table", "Hyperlink", "Bookmark", "TableOfContents", "Separator", "Header", "Footer", "PageSetup", "PageNumber", "Break", "InsertFootnote", "InsertEndnote", "Separator", "Find", "Separator", "Comments", "TrackChanges", "LocalClipboard", "RestrictEditing", "Separator", "FormFields", "UpdateFields", "ContentControl", "XML Mapping"],
13+
Paralegal: ["New", "Open", "Separator", "Undo", "Redo", "Separator", "Image", "Table", "Hyperlink", "Bookmark", "TableOfContents", "Separator", "Header", "Footer", "PageSetup", "PageNumber", "Break", "InsertFootnote", "InsertEndnote", "Separator", "Find", "Separator", "Comments", "TrackChanges", "LocalClipboard", "RestrictEditing", "Separator", "FormFields", "UpdateFields", "ContentControl", "XML Mapping"],
14+
Client: ["Comments", "Find"],
15+
Reviewer: ["Comments", "Find", "TrackChanges"],
16+
Admin: ["New", "Open", "Separator", "Undo", "Redo", "Separator", "Image", "Table", "Hyperlink", "Bookmark", "TableOfContents", "Separator", "Header", "Footer", "PageSetup", "PageNumber", "Break", "InsertFootnote", "InsertEndnote", "Separator", "Find", "Separator", "Comments", "TrackChanges", "LocalClipboard", "RestrictEditing", "Separator", "FormFields", "UpdateFields", "ContentControl", "XML Mapping"]
17+
};
18+
19+
// Create references for title bar and container
20+
const titleBarRef = useRef(null);
21+
const containerRef = useRef(null);
22+
23+
// Effect to initialize or update the document editor when the user changes
24+
useEffect(() => {
25+
if (user && containerRef.current) {
26+
convertDocxToSfdt();
27+
containerRef.current.documentEditor.documentName = "Document";
28+
containerRef.current.documentEditor.currentUser = user.email;
29+
30+
// Update document title when changes are made
31+
containerRef.current.documentChange = () => {
32+
titleBarRef.current.updateDocumentTitle();
33+
containerRef.current.documentEditor.focusIn();
34+
};
35+
if (!titleBarRef.current) {
36+
titleBarRef.current = new TitleBar(document.getElementById("documenteditor_titlebar"), containerRef.current.documentEditor, true);
37+
titleBarRef.current.updateDocumentTitle();
38+
}
39+
containerRef.current.toolbarItems = toolbarConfig[user.username];
40+
}
41+
}, [user]);
42+
43+
// Convert GitHub Raw document to SFDT and load in Editor.
44+
const convertDocxToSfdt = async () => {
45+
try {
46+
const docxResponse = await fetch('https://raw.githubusercontent.com/syncfusion/blazor-showcase-document-explorer/master/server/wwwroot/Files/Documents/Giant%20Panda.docx');
47+
const docxBlob = await docxResponse.blob();
48+
49+
const formData = new FormData();
50+
formData.append('files', docxBlob, 'GiantPanda.docx');
51+
52+
const importResponse = await fetch('https://ej2services.syncfusion.com/production/web-services/api/documenteditor/Import', {
53+
method: 'POST',
54+
body: formData,
55+
});
56+
57+
if (importResponse.ok) {
58+
defaultDocument = await importResponse.text();
59+
containerRef.current.documentEditor.open(defaultDocument);
60+
} else {
61+
console.error(`Failed to import document: ${importResponse.statusText}`);
62+
}
63+
} catch (error) {
64+
console.error('Error converting document:', error);
65+
}
66+
};
67+
68+
return (
69+
<div>
70+
<div className="main-titlebar" style={{ display: "flex", justifyContent: "space-between", padding: "8px 12px", alignItems: "center", background: "#f0f0f0" }}>
71+
<p className="welcome-text" style={{ margin: 0 }}>Welcome, {user.username || user.email}</p>
72+
<button className="logout-btn" onClick={onLogout}>Logout</button>
73+
</div>
74+
<div id="documenteditor_titlebar" className="e-de-ctn-title"></div>
75+
<style>
76+
{`.e-toolbar-items {
77+
display: flex !important;
78+
justify-content: center !important;
79+
}`}
80+
</style>
81+
<DocumentEditorContainerComponent
82+
ref={containerRef}
83+
id="container"
84+
height="calc(100vh - 92px)"
85+
enableToolbar={true}
86+
/>
87+
</div>
88+
);
89+
}
90+
91+
export default DocumentEditor;

0 commit comments

Comments
 (0)