first commit
This commit is contained in:
128
app/src/components/templates/CollaborationPopup.tsx
Normal file
128
app/src/components/templates/CollaborationPopup.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, { useState } from "react";
|
||||
import RenderOverlay from "./Overlay";
|
||||
import { ArrowIcon, CloseIcon } from "../icons/ExportCommonIcons";
|
||||
import { AccessOption, User } from "../../types/users";
|
||||
import RegularDropDown from "../ui/inputs/RegularDropDown";
|
||||
import { access } from "fs";
|
||||
import MultiEmailInvite from "../ui/inputs/MultiEmailInvite";
|
||||
|
||||
interface UserListTemplateProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
const UserListTemplate: React.FC<UserListTemplateProps> = ({ user }) => {
|
||||
const [accessSelection, setAccessSelection] = useState<string>(user.access);
|
||||
|
||||
function accessUpdate({ option }: AccessOption) {
|
||||
setAccessSelection(option);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="user-list-container">
|
||||
<div className="user-details">
|
||||
<div className="profile-image">
|
||||
{user.profileImage ? (
|
||||
<img
|
||||
src={user.profileImage || "https://via.placeholder.com/150"}
|
||||
alt={`${user.name}'s profile`}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="no-profile-container"
|
||||
style={{ background: user.color }}
|
||||
>
|
||||
{user.name[0]}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="user-name">{user.name}</div>
|
||||
</div>
|
||||
<div className="user-access">
|
||||
<RegularDropDown
|
||||
header={accessSelection}
|
||||
options={["Admin", "User", "Viewer"]}
|
||||
onSelect={(option) => accessUpdate({ option })}
|
||||
search={false}
|
||||
/>
|
||||
{/* <ArrowIcon /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CollaborationPopup: React.FC = () => {
|
||||
const users = [
|
||||
{
|
||||
name: "Alice Johnson",
|
||||
email: "alice.johnson@example.com",
|
||||
profileImage: "",
|
||||
color: "#FF6600",
|
||||
access: "Admin",
|
||||
},
|
||||
{
|
||||
name: "Bob Smith",
|
||||
email: "bob.smith@example.com",
|
||||
profileImage: "",
|
||||
color: "#488EF6",
|
||||
access: "Viewer",
|
||||
},
|
||||
{
|
||||
name: "Charlie Brown",
|
||||
email: "charlie.brown@example.com",
|
||||
profileImage: "",
|
||||
color: "#48AC2A",
|
||||
access: "Viewer",
|
||||
},
|
||||
{
|
||||
name: "Diana Prince",
|
||||
email: "diana.prince@example.com",
|
||||
profileImage: "",
|
||||
color: "#D44242",
|
||||
access: "Viewer",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<RenderOverlay>
|
||||
<div className="collaboration-popup-wrapper">
|
||||
<div className="collaboration-popup-container">
|
||||
<div className="header">
|
||||
<div className="content">Share this file</div>
|
||||
<div className="content">
|
||||
<div className="copy-link-button">copy link</div>
|
||||
<div className="close-button">
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="invite-input-container">
|
||||
<MultiEmailInvite />
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div className="access-and-user-control-container">
|
||||
<div className="user-header">Who has access</div>
|
||||
<div className="general-access-container">
|
||||
<div className="team-container">
|
||||
<div className="project-name">Untitled</div>
|
||||
<div className="number-of-peoples-have-access">
|
||||
{users.length} persons
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="users-list-container">
|
||||
<div className="you-container">
|
||||
<div className="your-name">User 1</div>
|
||||
<div className="indicater">you</div>
|
||||
</div>
|
||||
{users.map((user, index) => (
|
||||
<UserListTemplate key={index} user={user} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollaborationPopup;
|
||||
39
app/src/components/templates/LoadingPage.tsx
Normal file
39
app/src/components/templates/LoadingPage.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import RenderOverlay from "./Overlay";
|
||||
import { LogoIconLarge } from "../icons/Logo";
|
||||
|
||||
interface LoadingPageProps {
|
||||
progress: number; // Expect progress as a percentage (0-100)
|
||||
}
|
||||
|
||||
const LoadingPage: React.FC<LoadingPageProps> = ({ progress }) => {
|
||||
// Ensure progress stays within 0-100 range
|
||||
const validatedProgress = Math.min(100, Math.max(0, progress));
|
||||
|
||||
return (
|
||||
<RenderOverlay>
|
||||
<div className="loading-wrapper">
|
||||
<div className="loading-container">
|
||||
<div className="project-name">Untitled</div>
|
||||
<div className="loading-hero-container">
|
||||
<div className="logo">
|
||||
<LogoIconLarge />
|
||||
</div>
|
||||
<div className="content">Entering A New World of Dwinzo</div>
|
||||
</div>
|
||||
<div className="progress-container">
|
||||
<div className="progress-value">{validatedProgress}%</div>
|
||||
<div className="progress-indicator-container">
|
||||
<div
|
||||
className="progress-bar"
|
||||
style={{ width: `${validatedProgress}%` }} // Dynamic width
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingPage;
|
||||
21
app/src/components/templates/Overlay.tsx
Normal file
21
app/src/components/templates/Overlay.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
// Define the props interface for the component, which requires `children` as a ReactNode type
|
||||
interface PortalProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// The `RenderOverlay` component allows rendering of its children into a specific DOM node (`root-over`) outside of the regular React component tree
|
||||
const RenderOverlay = ({ children }: PortalProps) => {
|
||||
// Retrieve the DOM element with the id `root-over` which serves as the mounting point for the overlay content.
|
||||
const rootOver = document.getElementById("root-over");
|
||||
|
||||
// If the `rootOver` element exists in the DOM, use `ReactDOM.createPortal` to render the `children` inside this element
|
||||
// `ReactDOM.createPortal` allows rendering outside of the usual React component tree, useful for overlays, modals, and tooltips
|
||||
// If `rootOver` is null (i.e., does not exist), return null, rendering nothing
|
||||
return rootOver ? ReactDOM.createPortal(children, rootOver) : null;
|
||||
};
|
||||
|
||||
// Export the `RenderOverlay` component as the default export for use in other parts of the application
|
||||
export default RenderOverlay;
|
||||
74
app/src/components/templates/ToastProvider.tsx
Normal file
74
app/src/components/templates/ToastProvider.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { createContext, useContext, useState, ReactNode } from "react";
|
||||
|
||||
type Toast = {
|
||||
id: string;
|
||||
message: string;
|
||||
type: "success" | "error" | "info" | "warning";
|
||||
position?: ToastPosition; // Optional position for each toast
|
||||
};
|
||||
|
||||
type ToastPosition =
|
||||
| "top-left"
|
||||
| "top-center"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-center"
|
||||
| "bottom-right";
|
||||
|
||||
type ToastContextType = {
|
||||
addToast: (
|
||||
message: string,
|
||||
type: Toast["type"],
|
||||
position?: ToastPosition
|
||||
) => void;
|
||||
removeToast: (id: string) => void;
|
||||
};
|
||||
|
||||
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
||||
|
||||
const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
const addToast = (
|
||||
message: string,
|
||||
type: Toast["type"],
|
||||
position: ToastPosition = "bottom-center" // Default position
|
||||
) => {
|
||||
const id = Math.random().toString(36).substr(2, 9); // Generate a unique ID
|
||||
setToasts((prev) => [...prev, { id, message, type, position }]);
|
||||
|
||||
// Auto-remove toast after 3 seconds
|
||||
setTimeout(() => removeToast(id), 3000);
|
||||
};
|
||||
|
||||
const removeToast = (id: string) => {
|
||||
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ addToast, removeToast }}>
|
||||
{children}
|
||||
<div className={`toast-container ${"bottom-center"}`}>
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
className={`toast ${toast.type}`}
|
||||
onClick={() => removeToast(toast.id)} // Allow manual dismissal
|
||||
>
|
||||
{toast.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useToast = (): ToastContextType => {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error("useToast must be used within a ToastProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default ToastProvider;
|
||||
Reference in New Issue
Block a user