Add prettier and reformat project

This commit is contained in:
pmoharana-cmd 2024-04-23 09:42:15 -04:00
parent 3fd0a545e7
commit f2765ecb47
45 changed files with 6157 additions and 5172 deletions

View File

@ -1,3 +1,3 @@
{ {
"extends": "next/core-web-vitals" "extends": "next/core-web-vitals"
} }

6
compass/.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": false
}

View File

@ -1,36 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started ## Getting Started
First, run the development server: First, run the development server:
```bash ```bash
npm run dev npm run dev
# or # or
yarn dev yarn dev
# or # or
pnpm dev pnpm dev
# or # or
bun dev bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More ## Learn More
To learn more about Next.js, take a look at the following resources: To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel ## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,26 @@
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { createClient } from "@/utils/supabase/server";
export async function login(username: string, password: string) {
const supabase = createClient();
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: username,
password: password,
};
const { error } = await supabase.auth.signInWithPassword(data);
if (error) {
redirect("/auth/error");
}
revalidatePath("/resource", "layout");
redirect("/resource");
}

View File

@ -0,0 +1,3 @@
export default function ErrorPage() {
return <p>Sorry, something went wrong</p>;
}

View File

@ -1,25 +1,23 @@
// pages/forgot-password.tsx // pages/forgot-password.tsx
"use client"; "use client";
import React, { useState } from 'react'; import React, { useState } from "react";
import Input from '@/components/Input'; import Input from "@/components/Input";
import Button from '@/components/Button'; import Button from "@/components/Button";
import InlineLink from '@/components/InlineLink'; import InlineLink from "@/components/InlineLink";
import ErrorBanner from '@/components/auth/ErrorBanner'; import ErrorBanner from "@/components/auth/ErrorBanner";
export default function ForgotPasswordPage() { export default function ForgotPasswordPage() {
const [confirmEmail, setConfirmEmail] = useState(""); const [confirmEmail, setConfirmEmail] = useState("");
const [emailError, setEmailError] = useState<string | null>(null); const [emailError, setEmailError] = useState<string | null>(null);
function isValidEmail(email: string): boolean { function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email.trim() === '') { if (email.trim() === "") {
setEmailError('Email cannot be empty'); setEmailError("Email cannot be empty");
return false; return false;
} else if (!emailRegex.test(email)) { } else if (!emailRegex.test(email)) {
setEmailError('Invalid email format'); setEmailError("Invalid email format");
return false; return false;
} }
return true; // No error return true; // No error
@ -27,31 +25,30 @@ export default function ForgotPasswordPage() {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
isValidEmail(confirmEmail); isValidEmail(confirmEmail);
event.preventDefault(); event.preventDefault();
} };
return ( return (
<> <>
<h1 className="font-bold text-xl text-purple-800">Forgot Password</h1> <h1 className="font-bold text-xl text-purple-800">
<div className="mb-6"> Forgot Password
<Input </h1>
type='email' <div className="mb-6">
valid={emailError == null} <Input
title="Enter your email address" type="email"
placeholder="janedoe@gmail.com" valid={emailError == null}
value={confirmEmail} title="Enter your email address"
onChange={(e) => setConfirmEmail(e.target.value)} placeholder="janedoe@gmail.com"
/> value={confirmEmail}
</div> onChange={(e) => setConfirmEmail(e.target.value)}
{emailError && <ErrorBanner heading={emailError} />} />
<div className="flex flex-col items-left space-y-4"> </div>
<InlineLink href="/auth/login"> {emailError && <ErrorBanner heading={emailError} />}
Back to Sign In <div className="flex flex-col items-left space-y-4">
</InlineLink> <InlineLink href="/auth/login">Back to Sign In</InlineLink>
<Button type="submit" onClick={handleClick}> <Button type="submit" onClick={handleClick}>
Send Send
</Button> </Button>
</div> </div>
</> </>
); );
} }

View File

@ -1,22 +1,20 @@
import Paper from "@/components/auth/Paper";
import Paper from '@/components/auth/Paper';
export default function RootLayout({ export default function RootLayout({
// Layouts must accept a children prop. // Layouts must accept a children prop.
// This will be populated with nested layouts or pages // This will be populated with nested layouts or pages
children, children,
}: { }: {
children: React.ReactNode children: React.ReactNode;
}) { }) {
return ( return (
<Paper> <Paper>
<form className="mb-0 m-auto mt-6 space-y-4 border border-gray-200 rounded-lg p-4 shadow-lg sm:p-6 lg:p-8 bg-white max-w-xl"> <form className="mb-0 m-auto mt-6 space-y-4 border border-gray-200 rounded-lg p-4 shadow-lg sm:p-6 lg:p-8 bg-white max-w-xl">
{children} {children}
</form> </form>
<p className="text-center mt-6 text-gray-500 text-xs"> <p className="text-center mt-6 text-gray-500 text-xs">
&copy; 2024 Compass Center &copy; 2024 Compass Center
</p> </p>
</Paper> </Paper>
) );
} }

View File

@ -1,13 +1,13 @@
// pages/index.tsx // pages/index.tsx
"use client"; "use client";
import Button from '@/components/Button'; import Button from "@/components/Button";
import Input from '@/components/Input' import Input from "@/components/Input";
import InlineLink from '@/components/InlineLink'; import InlineLink from "@/components/InlineLink";
import Image from 'next/image'; import Image from "next/image";
import { useState } from "react"; import { useState } from "react";
import PasswordInput from '@/components/auth/PasswordInput'; import PasswordInput from "@/components/auth/PasswordInput";
import ErrorBanner from '@/components/auth/ErrorBanner'; import ErrorBanner from "@/components/auth/ErrorBanner";
export default function Page() { export default function Page() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@ -17,65 +17,71 @@ export default function Page() {
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.currentTarget.value); setEmail(event.currentTarget.value);
} };
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handlePasswordChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setPassword(event.currentTarget.value); setPassword(event.currentTarget.value);
} };
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// Priority: Incorrect combo > Missing email > Missing password // Priority: Incorrect combo > Missing email > Missing password
if (password.trim().length === 0) { if (password.trim().length === 0) {
setEmailError("Please enter your password.") setEmailError("Please enter your password.");
event.preventDefault(); event.preventDefault();
} }
// This shouldn't happen, <input type="email"> already provides validation, but just in case. // This shouldn't happen, <input type="email"> already provides validation, but just in case.
if (email.trim().length === 0) { if (email.trim().length === 0) {
setPasswordError("Please enter your email.") setPasswordError("Please enter your email.");
event.preventDefault(); event.preventDefault();
} }
// Placeholder for incorrect email + password combo. // Placeholder for incorrect email + password combo.
if (email === "incorrect@gmail.com" && password) { if (email === "incorrect@gmail.com" && password) {
setPasswordError("Incorrect password.") setPasswordError("Incorrect password.");
event.preventDefault(); event.preventDefault();
} }
} };
return ( return (
<> <>
<Image <Image
src="/logo.png" src="/logo.png"
alt='Compass Center logo.' alt="Compass Center logo."
width={100} width={100}
height={91} height={91}
/> />
<h1 className='font-bold text-2xl text-purple-800'>Login</h1> <h1 className="font-bold text-2xl text-purple-800">Login</h1>
<div className="mb-6"> <div className="mb-6">
<Input type='email' valid={emailError == ""} title="Email" placeholder="janedoe@gmail.com" onChange={handleEmailChange} required /> <Input
type="email"
</div> valid={emailError == ""}
{emailError && <ErrorBanner heading={emailError} />} title="Email"
placeholder="janedoe@gmail.com"
<div className="mb-6"> onChange={handleEmailChange}
<PasswordInput title="Password" valid={passwordError == ""} onChange={handlePasswordChange} /> required
/>
</div> </div>
{passwordError && <ErrorBanner heading={passwordError} />} {emailError && <ErrorBanner heading={emailError} />}
<div className="flex flex-col items-left space-y-4">
<InlineLink href="/auth/forgot_password">
Forgot password?
</InlineLink>
<Button onClick={handleClick}>
Login
</Button>
</div>
<div className="mb-6">
<PasswordInput
title="Password"
valid={passwordError == ""}
onChange={handlePasswordChange}
/>
</div>
{passwordError && <ErrorBanner heading={passwordError} />}
<div className="flex flex-col items-left space-y-4">
<InlineLink href="/auth/forgot_password">
Forgot password?
</InlineLink>
<Button onClick={handleClick}>Login</Button>
</div>
</> </>
); );
}; }

View File

@ -1,62 +1,79 @@
// pages/index.tsx // pages/index.tsx
"use client"; "use client";
import { useState, useEffect } from 'react'; import { useState, useEffect } from "react";
import Button from '@/components/Button'; import Button from "@/components/Button";
import PasswordInput from '@/components/auth/PasswordInput'; import PasswordInput from "@/components/auth/PasswordInput";
import ErrorBanner from '@/components/auth/ErrorBanner'; import ErrorBanner from "@/components/auth/ErrorBanner";
function isStrongPassword(password: string): boolean { function isStrongPassword(password: string): boolean {
const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/; const strongPasswordRegex =
return strongPasswordRegex.test(password); /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;
return strongPasswordRegex.test(password);
} }
export default function Page() { export default function Page() {
const [newPassword, setNewPassword] = useState(''); const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState("");
const [isButtonDisabled, setIsButtonDisabled] = useState(true); const [isButtonDisabled, setIsButtonDisabled] = useState(true);
useEffect(() => { useEffect(() => {
setIsButtonDisabled(newPassword === '' || confirmPassword === '' || newPassword !== confirmPassword|| !isStrongPassword(newPassword)); setIsButtonDisabled(
}, [newPassword, confirmPassword]); newPassword === "" ||
confirmPassword === "" ||
newPassword !== confirmPassword ||
!isStrongPassword(newPassword)
);
}, [newPassword, confirmPassword]);
return (
return ( <>
<> <div className="text-center sm:text-left">
<div className="text-center sm:text-left"> <h1 className="font-bold text-xl text-purple-800">
<h1 className="font-bold text-xl text-purple-800">New Password</h1> New Password
</div> </h1>
<div className="mb-4"> </div>
<PasswordInput <div className="mb-4">
title="Enter New Password" <PasswordInput
value={newPassword} title="Enter New Password"
valid={!isButtonDisabled || isStrongPassword(newPassword)} value={newPassword}
onChange={(e) => { valid={!isButtonDisabled || isStrongPassword(newPassword)}
setNewPassword(e.target.value); onChange={(e) => {
}} setNewPassword(e.target.value);
/> }}
</div> />
{isStrongPassword(newPassword) || newPassword === '' ? null : <ErrorBanner heading="Password is not strong enough." description="Tip: Use a mix of letters, numbers, and symbols for a strong password. Aim for at least 8 characters!" />} </div>
<div className="mb-6"> {isStrongPassword(newPassword) || newPassword === "" ? null : (
<PasswordInput <ErrorBanner
title="Confirm Password" heading="Password is not strong enough."
value={confirmPassword} description="Tip: Use a mix of letters, numbers, and symbols for a strong password. Aim for at least 8 characters!"
valid={!isButtonDisabled || (newPassword === confirmPassword && confirmPassword !== '')} />
onChange={(e) => { )}
setConfirmPassword(e.target.value); <div className="mb-6">
}} <PasswordInput
/> title="Confirm Password"
</div> value={confirmPassword}
{newPassword === confirmPassword || confirmPassword === '' ? null : <ErrorBanner heading="Passwords do not match." description="Please make sure both passwords are the exact same!"/>} valid={
<div className="flex flex-col items-left space-y-4"> !isButtonDisabled ||
<Button type="submit" disabled={isButtonDisabled} > (newPassword === confirmPassword &&
Send confirmPassword !== "")
</Button> }
</div> onChange={(e) => {
</> setConfirmPassword(e.target.value);
); }}
/>
</div>
{newPassword === confirmPassword ||
confirmPassword === "" ? null : (
<ErrorBanner
heading="Passwords do not match."
description="Please make sure both passwords are the exact same!"
/>
)}
<div className="flex flex-col items-left space-y-4">
<Button type="submit" disabled={isButtonDisabled}>
Send
</Button>
</div>
</>
);
} }

View File

@ -1,16 +1,15 @@
import '../styles/globals.css'; import "../styles/globals.css";
export default function RootLayout({
export default function RootLayout({ // Layouts must accept a children prop.
// Layouts must accept a children prop. // This will be populated with nested layouts or pages
// This will be populated with nested layouts or pages children,
children, }: {
}: { children: React.ReactNode;
children: React.ReactNode }) {
}) { return (
return ( <html lang="en">
<html lang="en"> <body>{children}</body>
<body>{children}</body> </html>
</html> );
) }
}

View File

@ -14,80 +14,87 @@ import { ChangeEvent, useState } from "react";
// } // }
export default function Page() { export default function Page() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
const testFetch = () => { const testFetch = () => {
const result = fetch("/api/health"); const result = fetch("/api/health");
console.log(result); console.log(result);
}; };
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.currentTarget.value); setEmail(event.currentTarget.value);
console.log("email " + email); console.log("email " + email);
}; };
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handlePasswordChange = (
setPassword(event.currentTarget.value); event: React.ChangeEvent<HTMLInputElement>
console.log("password " + password); ) => {
}; setPassword(event.currentTarget.value);
console.log("password " + password);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault(); event.preventDefault();
// Priority: Incorrect combo > Missing email > Missing password // Priority: Incorrect combo > Missing email > Missing password
if (password.trim().length === 0) { if (password.trim().length === 0) {
setError("Please enter your password."); setError("Please enter your password.");
} }
// This shouldn't happen, <input type="email"> already provides validation, but just in case. // This shouldn't happen, <input type="email"> already provides validation, but just in case.
if (email.trim().length === 0) { if (email.trim().length === 0) {
setError("Please enter your email."); setError("Please enter your email.");
} }
// Placeholder for incorrect email + password combo. // Placeholder for incorrect email + password combo.
if (email === "incorrect@gmail.com" && password) { if (email === "incorrect@gmail.com" && password) {
setError("Incorrect password."); setError("Incorrect password.");
} }
}; };
return ( return (
<> <>
<Paper> <Paper>
<form className="mb-0 m-auto mt-6 space-y-4 rounded-lg p-4 shadow-lg sm:p-6 lg:p-8 bg-white max-w-xl"> <form className="mb-0 m-auto mt-6 space-y-4 rounded-lg p-4 shadow-lg sm:p-6 lg:p-8 bg-white max-w-xl">
<Image <Image
src="/logo.png" src="/logo.png"
alt="Compass Center logo." alt="Compass Center logo."
width={100} width={100}
height={91} height={91}
/> />
<h1 className="font-bold text-xl text-purple-800">Login</h1> <h1 className="font-bold text-xl text-purple-800">Login</h1>
<div className="mb-4"> <div className="mb-4">
<Input <Input
type="email" type="email"
title="Email" title="Email"
placeholder="janedoe@gmail.com" placeholder="janedoe@gmail.com"
onChange={handleEmailChange} onChange={handleEmailChange}
/> />
</div> </div>
<div className="mb-6"> <div className="mb-6">
<Input <Input
type="password" type="password"
title="Password" title="Password"
onChange={handlePasswordChange} onChange={handlePasswordChange}
/> />
</div> </div>
<div className="flex flex-col items-left space-y-4"> <div className="flex flex-col items-left space-y-4">
<InlineLink href="/forgot_password">Forgot password?</InlineLink> <InlineLink href="/forgot_password">
<Button onClick={handleClick}>Login</Button> Forgot password?
<div className="text-center text-red-600" hidden={!error}> </InlineLink>
<p>{error}</p> <Button onClick={handleClick}>Login</Button>
</div> <div
</div> className="text-center text-red-600"
</form> hidden={!error}
<p className="text-center mt-6 text-gray-500 text-xs"> >
&copy; 2024 Compass Center <p>{error}</p>
</p> </div>
</Paper> </div>
</> </form>
); <p className="text-center mt-6 text-gray-500 text-xs">
&copy; 2024 Compass Center
</p>
</Paper>
</>
);
} }

View File

@ -1,14 +1,13 @@
"use client" "use client";
import Sidebar from '@/components/resource/Sidebar'; import Sidebar from "@/components/resource/Sidebar";
import React, { useState } from 'react'; import React, { useState } from "react";
import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline'; import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode children: React.ReactNode;
}) { }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
@ -18,20 +17,26 @@ export default function RootLayout({
<button <button
onClick={() => setIsSidebarOpen(!isSidebarOpen)} onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`} className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
aria-label={'Open sidebar'} aria-label={"Open sidebar"}
> >
{!isSidebarOpen && {
<ChevronDoubleRightIcon className="h-5 w-5" /> // Icon for closing the sidebar !isSidebarOpen && (
<ChevronDoubleRightIcon className="h-5 w-5" />
) // Icon for closing the sidebar
} }
</button> </button>
{/* sidebar */} {/* sidebar */}
<div className={`absolute inset-y-0 left-0 transform ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'} w-64 transition duration-300 ease-in-out`}> <div
className={`absolute inset-y-0 left-0 transform ${isSidebarOpen ? "translate-x-0" : "-translate-x-full"} w-64 transition duration-300 ease-in-out`}
>
<Sidebar setIsSidebarOpen={setIsSidebarOpen} /> <Sidebar setIsSidebarOpen={setIsSidebarOpen} />
</div> </div>
{/* page ui */} {/* page ui */}
<div className={`flex-1 transition duration-300 ease-in-out ${isSidebarOpen ? 'ml-64' : 'ml-0'}`}> <div
className={`flex-1 transition duration-300 ease-in-out ${isSidebarOpen ? "ml-64" : "ml-0"}`}
>
{children} {children}
</div> </div>
</div> </div>
) );
} }

View File

@ -1,9 +1,13 @@
"use client" "use client";
import Callout from "@/components/resource/Callout"; import Callout from "@/components/resource/Callout";
import Card from "@/components/resource/Card"; import Card from "@/components/resource/Card";
import { LandingSearchBar } from "@/components/resource/LandingSearchBar"; import { LandingSearchBar } from "@/components/resource/LandingSearchBar";
import { BookOpenIcon, BookmarkIcon, ClipboardIcon } from "@heroicons/react/24/solid"; import {
import Image from 'next/image'; BookOpenIcon,
BookmarkIcon,
ClipboardIcon,
} from "@heroicons/react/24/solid";
import Image from "next/image";
export default function Page() { export default function Page() {
return ( return (
@ -13,15 +17,26 @@ export default function Page() {
<div className="mb-4 flex items-center space-x-4"> <div className="mb-4 flex items-center space-x-4">
<Image <Image
src="/logo.png" src="/logo.png"
alt='Compass Center logo.' alt="Compass Center logo."
width={25} width={25}
height={25} height={25}
/> />
<h1 className='font-bold text-2xl text-purple-800'>Compass Center Advocate Landing Page</h1> <h1 className="font-bold text-2xl text-purple-800">
Compass Center Advocate Landing Page
</h1>
</div> </div>
<Callout> <Callout>
Welcome! Below you will find a list of resources for the Compass Center's trained advocates. These materials serve to virtually provide a collection of advocacy, resource, and hotline manuals and information. Welcome! Below you will find a list of resources for the
<b> If you are an advocate looking for the contact information of a particular Compass Center employee, please directly contact your staff back-up or the person in charge of your training.</b> Compass Center's trained advocates. These materials serve to
virtually provide a collection of advocacy, resource, and
hotline manuals and information.
<b>
{" "}
If you are an advocate looking for the contact
information of a particular Compass Center employee,
please directly contact your staff back-up or the person
in charge of your training.
</b>
</Callout> </Callout>
</div> </div>
<div className="p-8 flex-grow border-t border-gray-200 bg-gray-50"> <div className="p-8 flex-grow border-t border-gray-200 bg-gray-50">
@ -35,5 +50,5 @@ export default function Page() {
<LandingSearchBar /> <LandingSearchBar />
</div> </div>
</div> </div>
) );
} }

View File

@ -1,4 +1,4 @@
import { FunctionComponent, ReactNode } from 'react'; import { FunctionComponent, ReactNode } from "react";
type ButtonProps = { type ButtonProps = {
children: ReactNode; children: ReactNode;
@ -7,9 +7,13 @@ type ButtonProps = {
disabled?: boolean; disabled?: boolean;
}; };
const Button: FunctionComponent<ButtonProps> = ({
const Button: FunctionComponent<ButtonProps> = ({ children, type, disabled, onClick}) => { children,
const buttonClassName = `inline-block rounded border ${disabled ? 'bg-gray-400 text-gray-600 cursor-not-allowed' : 'border-purple-600 bg-purple-600 text-white hover:bg-transparent hover:text-purple-600 focus:outline-none focus:ring active:text-purple-500'} px-4 py-1 text-md font-semibold w-20 h-10 text-center`; type,
disabled,
onClick,
}) => {
const buttonClassName = `inline-block rounded border ${disabled ? "bg-gray-400 text-gray-600 cursor-not-allowed" : "border-purple-600 bg-purple-600 text-white hover:bg-transparent hover:text-purple-600 focus:outline-none focus:ring active:text-purple-500"} px-4 py-1 text-md font-semibold w-20 h-10 text-center`;
return ( return (
<button <button

View File

@ -1,16 +1,19 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from "react";
interface Link { interface Link {
href?: string; href?: string;
children: ReactNode; children: ReactNode;
} }
const InlineLink: React.FC<Link> = ({href = '#', children}) => { const InlineLink: React.FC<Link> = ({ href = "#", children }) => {
return ( return (
<a href={href} className='text-sm text-purple-600 hover:underline font-semibold'> <a
href={href}
className="text-sm text-purple-600 hover:underline font-semibold"
>
{children} {children}
</a> </a>
) );
} };
export default InlineLink; export default InlineLink;

View File

@ -1,37 +1,57 @@
import React, { FunctionComponent, InputHTMLAttributes, ReactNode, ChangeEvent } from 'react'; import React, {
FunctionComponent,
type InputProps = InputHTMLAttributes<HTMLInputElement> & { InputHTMLAttributes,
icon?: ReactNode; ReactNode,
title?: ReactNode; ChangeEvent,
type?:ReactNode; } from "react";
placeholder?:ReactNode
valid?:boolean; type InputProps = InputHTMLAttributes<HTMLInputElement> & {
onChange: (event: ChangeEvent<HTMLInputElement>) => void; icon?: ReactNode;
}; title?: ReactNode;
type?: ReactNode;
const Input: FunctionComponent<InputProps> = ({ icon, type, title, placeholder, onChange, valid = true, ...rest }) => { placeholder?: ReactNode;
return ( valid?: boolean;
<div> onChange: (event: ChangeEvent<HTMLInputElement>) => void;
<label };
htmlFor={title}
className={valid ? "block overflow-hidden rounded-md border border-gray-200 px-3 py-2 shadow-sm focus-within:border-purple-600 focus-within:ring-1 focus-within:ring-purple-600" : "block overflow-hidden rounded-md border border-gray-200 px-3 py-2 shadow-sm focus-within:border-red-600 focus-within:ring-1 focus-within:ring-red-600"} const Input: FunctionComponent<InputProps> = ({
> icon,
<span className="text-xs font-semibold text-gray-700"> {title} </span> type,
<div className="mt-1 flex items-center"> title,
<input placeholder,
type={type} onChange,
id={title} valid = true,
placeholder={placeholder} ...rest
onChange={onChange} }) => {
className="w-full border-none p-0 focus:border-transparent focus:outline-none focus:ring-0 sm:text-sm" return (
/> <div>
<span className="inline-flex items-center px-3 text-gray-500"> <label
{icon} htmlFor={title}
</span> className={
</div> valid
</label> ? "block overflow-hidden rounded-md border border-gray-200 px-3 py-2 shadow-sm focus-within:border-purple-600 focus-within:ring-1 focus-within:ring-purple-600"
</div> : "block overflow-hidden rounded-md border border-gray-200 px-3 py-2 shadow-sm focus-within:border-red-600 focus-within:ring-1 focus-within:ring-red-600"
); }
}; >
<span className="text-xs font-semibold text-gray-700">
export default Input; {" "}
{title}{" "}
</span>
<div className="mt-1 flex items-center">
<input
type={type}
id={title}
placeholder={placeholder}
onChange={onChange}
className="w-full border-none p-0 focus:border-transparent focus:outline-none focus:ring-0 sm:text-sm"
/>
<span className="inline-flex items-center px-3 text-gray-500">
{icon}
</span>
</div>
</label>
</div>
);
};
export default Input;

View File

@ -1,17 +1,27 @@
import React from 'react'; import React from "react";
interface ErrorBannerProps { interface ErrorBannerProps {
heading: string; heading: string;
description?: string | null; description?: string | null;
} }
const ErrorBanner: React.FC<ErrorBannerProps> = ({ heading, description = null }) => { const ErrorBanner: React.FC<ErrorBannerProps> = ({
heading,
description = null,
}) => {
return ( return (
<div role="alert" className="rounded border-s-4 border-red-500 bg-red-50 p-4"> <div
<strong className="block text-sm font-semibold text-red-800">{heading}</strong> role="alert"
{description && <p className="mt-2 text-xs font-thin text-red-700"> className="rounded border-s-4 border-red-500 bg-red-50 p-4"
{description} >
</p>} <strong className="block text-sm font-semibold text-red-800">
{heading}
</strong>
{description && (
<p className="mt-2 text-xs font-thin text-red-700">
{description}
</p>
)}
</div> </div>
); );
}; };

View File

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from "react";
interface PageInterface { interface PageInterface {
children: ReactNode; children: ReactNode;
@ -12,4 +12,4 @@ const Paper: React.FC<PageInterface> = ({ children }) => {
); );
}; };
export default Paper; export default Paper;

View File

@ -1,35 +1,46 @@
import React, { useState, FunctionComponent, ChangeEvent, ReactNode } from 'react'; import React, {
import Input from '../Input'; // Adjust the import path as necessary useState,
import { Icons } from '@/utils/constants'; FunctionComponent,
ChangeEvent,
ReactNode,
} from "react";
import Input from "../Input"; // Adjust the import path as necessary
import { Icons } from "@/utils/constants";
type PasswordInputProps = { type PasswordInputProps = {
title?: ReactNode; // Assuming you might want to reuse title, placeholder etc. title?: ReactNode; // Assuming you might want to reuse title, placeholder etc.
placeholder?: ReactNode; placeholder?: ReactNode;
valid?: boolean; valid?: boolean;
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
}; };
const PasswordInput: FunctionComponent<PasswordInputProps> = ({ onChange, valid = true, ...rest }) => { const PasswordInput: FunctionComponent<PasswordInputProps> = ({
const [visible, setVisible] = useState(false); onChange,
valid = true,
...rest
}) => {
const [visible, setVisible] = useState(false);
const toggleVisibility = () => { const toggleVisibility = () => {
setVisible(!visible); setVisible(!visible);
}; };
const PasswordIcon = visible ? Icons['HidePasswordIcon'] : Icons['UnhidePasswordIcon']; const PasswordIcon = visible
? Icons["HidePasswordIcon"]
: Icons["UnhidePasswordIcon"];
// Render the Input component and pass the PasswordIcon as an icon prop // Render the Input component and pass the PasswordIcon as an icon prop
return ( return (
<Input <Input
{...rest} {...rest}
type={visible ? "text" : "password"} type={visible ? "text" : "password"}
onChange={onChange} onChange={onChange}
valid={valid} valid={valid}
icon={ icon={
<PasswordIcon className="h-5 w-5" onClick={toggleVisibility} /> <PasswordIcon className="h-5 w-5" onClick={toggleVisibility} />
} }
/> />
); );
}; };
export default PasswordInput; export default PasswordInput;

View File

@ -1,15 +1,15 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
interface CalloutProps { interface CalloutProps {
children: ReactNode; children: ReactNode;
} }
const Callout = ({ children }: CalloutProps) => { const Callout = ({ children }: CalloutProps) => {
return ( return (
<div className="p-4 mb-4 flex items-center bg-purple-50 rounded-sm"> <div className="p-4 mb-4 flex items-center bg-purple-50 rounded-sm">
<span className="text-sm text-gray-800">{children}</span> <span className="text-sm text-gray-800">{children}</span>
</div> </div>
); );
}; };
export default Callout; export default Callout;

View File

@ -1,20 +1,17 @@
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
interface TagProps { interface TagProps {
text: string; text: string;
icon: React.ReactNode; icon: React.ReactNode;
} }
const Card: React.FC<TagProps> = ({ text, icon }) => { const Card: React.FC<TagProps> = ({ text, icon }) => {
return ( return (
<div className="flex flex-row space-x-2 items-start justify-start border border-gray-200 bg-white hover:bg-gray-50 shadow rounded-md p-4"> <div className="flex flex-row space-x-2 items-start justify-start border border-gray-200 bg-white hover:bg-gray-50 shadow rounded-md p-4">
<span className="h-5 text-purple-700 w-5"> <span className="h-5 text-purple-700 w-5">{icon}</span>
{icon} <span className="text-sm text-gray-800 font-semibold">{text}</span>
</span> </div>
<span className="text-sm text-gray-800 font-semibold">{text}</span> );
</div>
);
}; };
export default Card; export default Card;

View File

@ -1,48 +1,60 @@
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid" import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid";
import React, { useState } from 'react'; import React, { useState } from "react";
import Image from 'next/image'; import Image from "next/image";
export const LandingSearchBar: React.FC = () => { export const LandingSearchBar: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState("");
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value); setSearchTerm(event.target.value);
}; };
const clearSearch = () => { const clearSearch = () => {
setSearchTerm(''); setSearchTerm("");
}; };
return ( return (
<div className="max-w mx-auto"> <div className="max-w mx-auto">
{/* searchbar */} {/* searchbar */}
<div className="flex items-center bg-white border border-gray-200 shadow rounded-md"> <div className="flex items-center bg-white border border-gray-200 shadow rounded-md">
<div className="flex-grow"> <div className="flex-grow">
<input <input
className="sm:text-sm text-gray-800 w-full px-6 py-3 rounded-md focus:outline-none" className="sm:text-sm text-gray-800 w-full px-6 py-3 rounded-md focus:outline-none"
type="text" type="text"
placeholder="Search..." placeholder="Search..."
value={searchTerm} value={searchTerm}
onChange={handleSearchChange} onChange={handleSearchChange}
/> />
</div>
{/* input */}
{searchTerm && (
<button onClick={clearSearch}>
<XMarkIcon
className="h-5 w-5 text-gray-500"
aria-hidden="true"
/>
</button>
)}
<div className="p-3">
<MagnifyingGlassIcon
className="h-5 w-5 text-gray-500"
aria-hidden="true"
/>
</div>
</div>
{/* search results, for now since it's empty this is the default screen */}
<div className="flex flex-col pt-16 space-y-2 justify-center items-center">
<Image
alt="Landing illustration"
src="/landing_illustration.png"
width={250}
height={250}
/>
<h2 className="font-medium text-medium text-gray-800">
Need to find something? Use the links or the search bar
above to get your results.
</h2>
</div>
</div> </div>
{/* input */} );
{searchTerm && (
<button
onClick={clearSearch}
>
<XMarkIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
</button>
)}
<div className="p-3">
<MagnifyingGlassIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
</div>
</div>
{/* search results, for now since it's empty this is the default screen */}
<div className="flex flex-col pt-16 space-y-2 justify-center items-center">
<Image alt="Landing illustration" src="/landing_illustration.png" width={250} height={250} />
<h2 className="font-medium text-medium text-gray-800">Need to find something? Use the links or the search bar above to get your results.</h2>
</div>
</div>
);
}; };

View File

@ -1,46 +1,54 @@
import React from 'react'; import React from "react";
import { HomeIcon, ChevronDoubleLeftIcon, BookmarkIcon, ClipboardIcon, BookOpenIcon } from '@heroicons/react/24/solid'; import {
import { SidebarItem } from './SidebarItem'; HomeIcon,
import { UserProfile } from './UserProfile'; ChevronDoubleLeftIcon,
BookmarkIcon,
ClipboardIcon,
BookOpenIcon,
} from "@heroicons/react/24/solid";
import { SidebarItem } from "./SidebarItem";
import { UserProfile } from "./UserProfile";
interface SidebarProps { interface SidebarProps {
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>; setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
} }
const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => { const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
return ( return (
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4"> <div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
{/* button to close sidebar */} {/* button to close sidebar */}
<div className="flex justify-end"> <div className="flex justify-end">
<button <button
onClick={() => setIsSidebarOpen(false)} onClick={() => setIsSidebarOpen(false)}
className="py-2 text-gray-500 hover:text-gray-800" className="py-2 text-gray-500 hover:text-gray-800"
aria-label="Close sidebar" aria-label="Close sidebar"
> >
<ChevronDoubleLeftIcon className="h-5 w-5" /> <ChevronDoubleLeftIcon className="h-5 w-5" />
</button> </button>
</div> </div>
<div className="flex flex-col space-y-8"> <div className="flex flex-col space-y-8">
{/* user + logout button */}
{/* user + logout button */} <div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md "> <UserProfile />
</div>
<UserProfile /> {/* navigation menu */}
<div className="flex flex-col space-y-2">
<h4 className="text-xs font-semibold text-gray-500">
Pages
</h4>
<nav className="flex flex-col">
<SidebarItem icon={<HomeIcon />} text="Home" />
<SidebarItem icon={<BookmarkIcon />} text="Resources" />
<SidebarItem icon={<ClipboardIcon />} text="Services" />
<SidebarItem
icon={<BookOpenIcon />}
text="Training Manuals"
/>
</nav>
</div>
</div>
</div> </div>
{/* navigation menu */} );
<div className="flex flex-col space-y-2">
<h4 className="text-xs font-semibold text-gray-500">Pages</h4>
<nav className="flex flex-col">
<SidebarItem icon={<HomeIcon />} text="Home" />
<SidebarItem icon={<BookmarkIcon />} text="Resources" />
<SidebarItem icon={<ClipboardIcon />} text="Services" />
<SidebarItem icon={<BookOpenIcon />} text="Training Manuals" />
</nav>
</div>
</div>
</div>
);
}; };
export default Sidebar;
export default Sidebar;

View File

@ -1,16 +1,18 @@
interface SidebarItemProps { interface SidebarItemProps {
icon: React.ReactElement; icon: React.ReactElement;
text: string; text: string;
} }
export const SidebarItem: React.FC<SidebarItemProps> = ({ icon, text }) => { export const SidebarItem: React.FC<SidebarItemProps> = ({ icon, text }) => {
return ( return (
<a href="#" className="flex items-center p-2 space-x-2 hover:bg-gray-200 rounded-md"> <a
<span className="h-5 text-gray-500 w-5"> href="#"
{icon} className="flex items-center p-2 space-x-2 hover:bg-gray-200 rounded-md"
</span> >
<span className="flex-grow font-medium text-xs text-gray-500">{text}</span> <span className="h-5 text-gray-500 w-5">{icon}</span>
</a> <span className="flex-grow font-medium text-xs text-gray-500">
); {text}
}; </span>
</a>
);
};

View File

@ -1,11 +1,15 @@
export const UserProfile = () => { export const UserProfile = () => {
return ( return (
<div className="flex flex-col items-start space-y-2"> <div className="flex flex-col items-start space-y-2">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-sm font-semibold text-gray-800">Compass Center</span> <span className="text-sm font-semibold text-gray-800">
<span className="text-xs text-gray-500">cssgunc@gmail.com</span> Compass Center
</span>
<span className="text-xs text-gray-500">cssgunc@gmail.com</span>
</div>
<button className="text-red-600 font-semibold text-xs hover:underline mt-1">
Sign out
</button>
</div> </div>
<button className="text-red-600 font-semibold text-xs hover:underline mt-1">Sign out</button> );
</div> };
)
}

View File

@ -1,8 +1,8 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
images: { images: {
domains: ['notioly.com'] domains: ["notioly.com"],
}, },
} };
module.exports = nextConfig module.exports = nextConfig;

9484
compass/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,43 @@
{ {
"name": "compass", "name": "compass",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
}, "prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
"dependencies": { },
"@heroicons/react": "^2.1.1", "dependencies": {
"next": "13.5.6", "@heroicons/react": "^2.1.1",
"react": "^18", "@supabase/ssr": "^0.3.0",
"react-dom": "^18" "@supabase/supabase-js": "^2.42.3",
}, "next": "13.5.6",
"devDependencies": { "react": "^18",
"@types/node": "^20", "react-dom": "^18"
"@types/react": "^18", },
"@types/react-dom": "^18", "devDependencies": {
"autoprefixer": "^10", "@types/node": "^20",
"eslint": "^8", "@types/react": "^18",
"eslint-config-next": "13.5.6", "@types/react-dom": "^18",
"postcss": "^8", "autoprefixer": "^10",
"tailwindcss": "^3", "eslint": "^8",
"typescript": "^5" "eslint-config-next": "13.5.6",
} "husky": "^9.0.11",
} "lint-staged": "^15.2.2",
"postcss": "^8",
"tailwindcss": "^3",
"typescript": "^5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write"
]
}
}

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View File

@ -1,8 +1,7 @@
/* globals.css */ /* globals.css */
@import 'tailwindcss/base'; @import "tailwindcss/base";
@import 'tailwindcss/components'; @import "tailwindcss/components";
@import 'tailwindcss/utilities'; @import "tailwindcss/utilities";
:root { :root {
/* Colors */ /* Colors */
@ -11,7 +10,8 @@
--ring-opacity: 0.5; --ring-opacity: 0.5;
/* Shadows */ /* Shadows */
--shadow-default: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); --shadow-default: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-focus: 0 0 0 3px rgba(66, 153, 225, 0.5); --shadow-focus: 0 0 0 3px rgba(66, 153, 225, 0.5);
/* Borders */ /* Borders */
@ -31,66 +31,65 @@
/* A shade of gray */ /* A shade of gray */
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('/fonts/Inter-Regular.ttf') format('truetype'); src: url("/fonts/Inter-Regular.ttf") format("truetype");
} }
/* Inter-Bold */ /* Inter-Bold */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url('/fonts/Inter-Bold.ttf') format('truetype'); src: url("/fonts/Inter-Bold.ttf") format("truetype");
} }
/* Inter-Black */ /* Inter-Black */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
src: url('/fonts/Inter-Black.ttf') format('truetype'); src: url("/fonts/Inter-Black.ttf") format("truetype");
} }
/* Inter-ExtraBold */ /* Inter-ExtraBold */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
src: url('/fonts/Inter-ExtraBold.ttf') format('truetype'); src: url("/fonts/Inter-ExtraBold.ttf") format("truetype");
} }
/* Inter-ExtraLight */ /* Inter-ExtraLight */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 200; font-weight: 200;
src: url('/fonts/Inter-ExtraLight.ttf') format('truetype'); src: url("/fonts/Inter-ExtraLight.ttf") format("truetype");
} }
/* Inter-Medium */ /* Inter-Medium */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: url('/fonts/Inter-Medium.ttf') format('truetype'); src: url("/fonts/Inter-Medium.ttf") format("truetype");
} }
/* Inter-SemiBold */ /* Inter-SemiBold */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url('/fonts/Inter-SemiBold.ttf') format('truetype'); src: url("/fonts/Inter-SemiBold.ttf") format("truetype");
} }
/* Inter-Thin */ /* Inter-Thin */
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 100; font-weight: 100;
src: url('/fonts/Inter-Thin.ttf') format('truetype'); src: url("/fonts/Inter-Thin.ttf") format("truetype");
} }

View File

@ -1,26 +1,27 @@
import type { Config } from 'tailwindcss'; import type { Config } from "tailwindcss";
const config: Config = { const config: Config = {
content: [ content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}', "./pages/**/*.{js,ts,jsx,tsx,mdx}",
'./components/**/*.{js,ts,jsx,tsx,mdx}', "./components/**/*.{js,ts,jsx,tsx,mdx}",
'./app/**/*.{js,ts,jsx,tsx,mdx}', "./app/**/*.{js,ts,jsx,tsx,mdx}",
], ],
theme: { theme: {
extend: { extend: {
backgroundImage: { backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', "gradient-conic":
}, "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
fontFamily: { },
'sans': ['Inter', 'sans-serif'], // Add 'Inter' to the fontFamily theme fontFamily: {
}, sans: ["Inter", "sans-serif"], // Add 'Inter' to the fontFamily theme
fontWeight: { },
'medium': 500, // Ensure medium is correctly set to 500 fontWeight: {
} medium: 500, // Ensure medium is correctly set to 500
},
},
}, },
}, plugins: [],
plugins: [],
}; };
export default config; export default config;

View File

@ -1,27 +1,27 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
"name": "next" "name": "next"
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View File

@ -1,16 +1,15 @@
class CollectionImpl { class CollectionImpl {
title: string; title: string;
icon: any; icon: any;
data: any; data: any;
constructor(title: string, icon: any) { constructor(title: string, icon: any) {
this.title = title; this.title = title;
this.icon = icon; this.icon = icon;
} }
// subject to change // subject to change
setData(data: any){ setData(data: any) {
this.data = data; this.data = data;
} }
}
}

View File

@ -1,15 +1,15 @@
import { Icons } from "../constants"; import { Icons } from "../constants";
export class Field { export class Field {
iconKey: keyof typeof Icons; iconKey: keyof typeof Icons;
title: string; title: string;
constructor(iconKey: keyof typeof Icons, title: string) { constructor(iconKey: keyof typeof Icons, title: string) {
this.iconKey = iconKey; this.iconKey = iconKey;
this.title = title; this.title = title;
} }
validateInput(value: any): boolean { validateInput(value: any): boolean {
return value !== null; return value !== null;
} }
} }

View File

@ -1,49 +1,68 @@
import { ListBulletIcon, HashtagIcon, Bars3BottomLeftIcon, EnvelopeIcon, AtSymbolIcon, ClipboardIcon, ArrowsUpDownIcon, ChevronDoubleRightIcon, ChevronDoubleLeftIcon, ChevronRightIcon, ChevronLeftIcon, EyeIcon, EyeSlashIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon, LinkIcon, ClipboardDocumentCheckIcon } from '@heroicons/react/24/solid'; import {
ListBulletIcon,
HashtagIcon,
Bars3BottomLeftIcon,
EnvelopeIcon,
AtSymbolIcon,
ClipboardIcon,
ArrowsUpDownIcon,
ChevronDoubleRightIcon,
ChevronDoubleLeftIcon,
ChevronRightIcon,
ChevronLeftIcon,
EyeIcon,
EyeSlashIcon,
UserIcon,
BookOpenIcon,
MagnifyingGlassIcon,
LinkIcon,
ClipboardDocumentCheckIcon,
} from "@heroicons/react/24/solid";
export const Icons = { export const Icons = {
EmailInputIcon: EnvelopeIcon, EmailInputIcon: EnvelopeIcon,
HidePasswordIcon: EyeSlashIcon, HidePasswordIcon: EyeSlashIcon,
UnhidePasswordIcon: EyeIcon, UnhidePasswordIcon: EyeIcon,
UserIcon: UserIcon, UserIcon: UserIcon,
ResourceIcon: BookOpenIcon, ResourceIcon: BookOpenIcon,
SearchIcon: MagnifyingGlassIcon, SearchIcon: MagnifyingGlassIcon,
ServiceIcon: ClipboardIcon, ServiceIcon: ClipboardIcon,
CloseRightArrow: ChevronDoubleRightIcon, CloseRightArrow: ChevronDoubleRightIcon,
CloseLeftArrow: ChevronDoubleLeftIcon, CloseLeftArrow: ChevronDoubleLeftIcon,
LinkRightArrow:ChevronRightIcon, LinkRightArrow: ChevronRightIcon,
LinkLeftArrow:ChevronLeftIcon, LinkLeftArrow: ChevronLeftIcon,
SortIcon: ArrowsUpDownIcon, SortIcon: ArrowsUpDownIcon,
EmailTableIcon:AtSymbolIcon, EmailTableIcon: AtSymbolIcon,
LinkTableIcon: LinkIcon, LinkTableIcon: LinkIcon,
TextTableIcon: Bars3BottomLeftIcon, TextTableIcon: Bars3BottomLeftIcon,
NumberTableIcon: HashtagIcon, NumberTableIcon: HashtagIcon,
MultiselectTableIcon: ListBulletIcon, MultiselectTableIcon: ListBulletIcon,
RequirementsTableIcon: ClipboardDocumentCheckIcon RequirementsTableIcon: ClipboardDocumentCheckIcon,
}; };
export enum USER { export enum USER {
ADMIN, ADMIN,
EMPLOYEE, EMPLOYEE,
VOLUNTEER VOLUNTEER,
} }
export enum COLLECTION { export enum COLLECTION {
RESOURCE, RESOURCE,
SERVICE, SERVICE,
USER, USER,
TRAINING_MANUAL TRAINING_MANUAL,
} }
export enum PROGRAM { export enum PROGRAM {
DOMESTIC_VIOLENCE, DOMESTIC_VIOLENCE,
ECONOMIC_STABILITY, ECONOMIC_STABILITY,
COMMUNITY_EDUCATION COMMUNITY_EDUCATION,
} }
export enum STATUS { export enum STATUS {
FULL, FULL,
CLOSED, CLOSED,
ACCEPTING_CLIENTS ACCEPTING_CLIENTS,
} }
export enum DATATYPE { export enum DATATYPE {
@ -52,7 +71,5 @@ export enum DATATYPE {
LINK, LINK,
EMAIL, EMAIL,
MULTISELECT, MULTISELECT,
SELECT SELECT,
} }

View File

@ -2,136 +2,142 @@ import { PROGRAM, STATUS, USER } from "../constants";
const serviceEntries = [ const serviceEntries = [
{ {
name: "Empowerment Workshops", name: "Empowerment Workshops",
status: [STATUS.ACCEPTING_CLIENTS], status: [STATUS.ACCEPTING_CLIENTS],
summary: "Workshops to empower victims through education and skill-building.", summary:
requirements: "Resident of the community and victim of domestic violence.", "Workshops to empower victims through education and skill-building.",
program: [PROGRAM.DOMESTIC_VIOLENCE, PROGRAM.COMMUNITY_EDUCATION], requirements:
tags: ["empowerment", "education"], "Resident of the community and victim of domestic violence.",
program: [PROGRAM.DOMESTIC_VIOLENCE, PROGRAM.COMMUNITY_EDUCATION],
tags: ["empowerment", "education"],
}, },
{ {
name: "Financial Literacy Courses", name: "Financial Literacy Courses",
status: [STATUS.ACCEPTING_CLIENTS, STATUS.FULL], status: [STATUS.ACCEPTING_CLIENTS, STATUS.FULL],
summary: "Courses aimed at improving financial independence for victims.", summary:
requirements: "Open to all domestic violence victims.", "Courses aimed at improving financial independence for victims.",
program: [PROGRAM.ECONOMIC_STABILITY], requirements: "Open to all domestic violence victims.",
tags: ["finance", "literacy"], program: [PROGRAM.ECONOMIC_STABILITY],
tags: ["finance", "literacy"],
}, },
{ {
name: "Counseling Services", name: "Counseling Services",
status: [STATUS.ACCEPTING_CLIENTS], status: [STATUS.ACCEPTING_CLIENTS],
summary: "Professional counseling for individuals and families affected by domestic violence.", summary:
requirements: "Appointment required.", "Professional counseling for individuals and families affected by domestic violence.",
program: [PROGRAM.DOMESTIC_VIOLENCE], requirements: "Appointment required.",
tags: ["counseling", "mental health"], program: [PROGRAM.DOMESTIC_VIOLENCE],
tags: ["counseling", "mental health"],
}, },
{ {
name: "Job Placement Program", name: "Job Placement Program",
status: [STATUS.ACCEPTING_CLIENTS], status: [STATUS.ACCEPTING_CLIENTS],
summary: "Assistance with job search and placement for survivors.", summary: "Assistance with job search and placement for survivors.",
requirements: "Must be actively seeking employment.", requirements: "Must be actively seeking employment.",
program: [PROGRAM.ECONOMIC_STABILITY], program: [PROGRAM.ECONOMIC_STABILITY],
tags: ["job", "employment"], tags: ["job", "employment"],
}, },
{ {
name: "Legal Advocacy", name: "Legal Advocacy",
status: [STATUS.FULL], status: [STATUS.FULL],
summary: "Legal advice and representation for victims of domestic violence.", summary:
requirements: "Legal documentation of domestic violence required.", "Legal advice and representation for victims of domestic violence.",
program: [PROGRAM.DOMESTIC_VIOLENCE], requirements: "Legal documentation of domestic violence required.",
tags: ["legal", "advocacy"], program: [PROGRAM.DOMESTIC_VIOLENCE],
} tags: ["legal", "advocacy"],
]; },
];
const resourceEntries = [ const resourceEntries = [
{ {
name: "Legal Aid Reference", name: "Legal Aid Reference",
summary: "Comprehensive list of legal resources for victims.", summary: "Comprehensive list of legal resources for victims.",
link: "https://legalaid.example.com", link: "https://legalaid.example.com",
program: [PROGRAM.DOMESTIC_VIOLENCE], program: [PROGRAM.DOMESTIC_VIOLENCE],
tags: ["legal", "aid"], tags: ["legal", "aid"],
}, },
{ {
name: "Shelter Locations", name: "Shelter Locations",
summary: "Directory of safe shelters for victims escaping abuse.", summary: "Directory of safe shelters for victims escaping abuse.",
link: "https://shelters.example.com", link: "https://shelters.example.com",
program: [PROGRAM.DOMESTIC_VIOLENCE], program: [PROGRAM.DOMESTIC_VIOLENCE],
tags: ["shelter", "safety"], tags: ["shelter", "safety"],
}, },
{ {
name: "Support Group Finder", name: "Support Group Finder",
summary: "Find local support groups for survivors of domestic violence.", summary:
link: "https://supportgroups.example.com", "Find local support groups for survivors of domestic violence.",
program: [PROGRAM.COMMUNITY_EDUCATION], link: "https://supportgroups.example.com",
tags: ["support", "community"], program: [PROGRAM.COMMUNITY_EDUCATION],
tags: ["support", "community"],
}, },
{ {
name: "Employment Services", name: "Employment Services",
summary: "Resources for job training and placement services.", summary: "Resources for job training and placement services.",
link: "https://employment.example.com", link: "https://employment.example.com",
program: [PROGRAM.ECONOMIC_STABILITY], program: [PROGRAM.ECONOMIC_STABILITY],
tags: ["job", "training"], tags: ["job", "training"],
}, },
{ {
name: "Educational Workshops", name: "Educational Workshops",
summary: "Schedule of educational workshops on various topics.", summary: "Schedule of educational workshops on various topics.",
link: "https://workshops.example.com", link: "https://workshops.example.com",
program: [PROGRAM.COMMUNITY_EDUCATION], program: [PROGRAM.COMMUNITY_EDUCATION],
tags: ["education", "workshops"], tags: ["education", "workshops"],
} },
]; ];
const userEntries = [ const userEntries = [
{ {
name: "Alex Johnson", name: "Alex Johnson",
role: [USER.VOLUNTEER], role: [USER.VOLUNTEER],
email: "alex.johnson@example.com", email: "alex.johnson@example.com",
program: [PROGRAM.DOMESTIC_VIOLENCE], program: [PROGRAM.DOMESTIC_VIOLENCE],
experience: 2, experience: 2,
group: "Volunteer Group A", group: "Volunteer Group A",
}, },
{ {
name: "Sam Lee", name: "Sam Lee",
role: [USER.EMPLOYEE], role: [USER.EMPLOYEE],
email: "sam.lee@example.com", email: "sam.lee@example.com",
program: [PROGRAM.ECONOMIC_STABILITY], program: [PROGRAM.ECONOMIC_STABILITY],
experience: 5, experience: 5,
group: "Economic Support Team", group: "Economic Support Team",
}, },
{ {
name: "Jordan Smith", name: "Jordan Smith",
role: [USER.ADMIN, USER.VOLUNTEER], role: [USER.ADMIN, USER.VOLUNTEER],
email: "jordan.smith@example.com", email: "jordan.smith@example.com",
program: [PROGRAM.COMMUNITY_EDUCATION, PROGRAM.DOMESTIC_VIOLENCE], program: [PROGRAM.COMMUNITY_EDUCATION, PROGRAM.DOMESTIC_VIOLENCE],
experience: 3, experience: 3,
group: "Outreach and Education", group: "Outreach and Education",
}, },
{ {
name: "Casey Martinez", name: "Casey Martinez",
role: [USER.VOLUNTEER], role: [USER.VOLUNTEER],
email: "casey.martinez@example.com", email: "casey.martinez@example.com",
program: [PROGRAM.ECONOMIC_STABILITY], program: [PROGRAM.ECONOMIC_STABILITY],
experience: 1, experience: 1,
group: "Financial Literacy Volunteers", group: "Financial Literacy Volunteers",
}, },
{ {
name: "Jamie Chung", name: "Jamie Chung",
role: [USER.EMPLOYEE], role: [USER.EMPLOYEE],
email: "jamie.chung@example.com", email: "jamie.chung@example.com",
program: [PROGRAM.DOMESTIC_VIOLENCE], program: [PROGRAM.DOMESTIC_VIOLENCE],
experience: 4, experience: 4,
group: "Counseling Services Team", group: "Counseling Services Team",
} },
]; ];
export const mockFetchServices = () => { export const mockFetchServices = () => {
return serviceEntries; return serviceEntries;
} };
export const mockFetchResources = () => { export const mockFetchResources = () => {
return resourceEntries; return resourceEntries;
} };
export const mockFetchUsers = () => { export const mockFetchUsers = () => {
return userEntries; return userEntries;
} };

View File

@ -15,19 +15,21 @@ export class CollectionDataImpl {
for (const header of this.headers) { for (const header of this.headers) {
const value = row[header.title]; const value = row[header.title];
if (!header.validateInput(value)) { if (!header.validateInput(value)) {
console.error(`Validation failed for ${header.title} with value ${value}`); console.error(
`Validation failed for ${header.title} with value ${value}`
);
isValidRow = false; isValidRow = false;
break; break;
} }
} }
if (isValidRow) { if (isValidRow) {
this.rows.push(row); this.rows.push(row);
} else { } else {
console.log('Row not added due to validation failure.'); console.log("Row not added due to validation failure.");
} }
} }
getRows(): Record<string, any>[] { getRows(): Record<string, any>[] {
return this.rows; return this.rows;
} }
@ -35,5 +37,4 @@ export class CollectionDataImpl {
getHeaders(): Field[] { getHeaders(): Field[] {
return this.headers; return this.headers;
} }
}
}

View File

@ -10,6 +10,4 @@ export class CollectionImpl {
this.icon = icon; this.icon = icon;
this.data = data; this.data = data;
} }
}
}

View File

@ -1,18 +1,15 @@
import { Field } from "@/utils/classes/Field"; import { Field } from "@/utils/classes/Field";
export class EmailFieldImpl extends Field { export class EmailFieldImpl extends Field {
constructor() { constructor() {
super('EmailTableIcon', "Email"); super("EmailTableIcon", "Email");
} }
validateInput(value: any) : boolean { validateInput(value: any): boolean {
if (typeof value !== 'string') { if (typeof value !== "string") {
return false; return false;
} }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value); return emailRegex.test(value);
} }
}
}

View File

@ -1,15 +1,12 @@
import { Icons } from "@/utils/constants"; import { Icons } from "@/utils/constants";
import { Field } from "@/utils/classes/Field"; import { Field } from "@/utils/classes/Field";
export class IntegerFieldImpl extends Field { export class IntegerFieldImpl extends Field {
constructor(title: string) { constructor(title: string) {
super('NumberTableIcon', title); super("NumberTableIcon", title);
} }
validateInput(value: any) : boolean { validateInput(value: any): boolean {
return Number.isInteger(value); return Number.isInteger(value);
} }
}
}

View File

@ -1,18 +1,16 @@
import { Field } from "@/utils/classes/Field"; import { Field } from "@/utils/classes/Field";
export class LinkFieldImpl extends Field { export class LinkFieldImpl extends Field {
constructor() { constructor() {
super('LinkTableIcon', "Link"); super("LinkTableIcon", "Link");
} }
validateInput(value: any) : boolean { validateInput(value: any): boolean {
if (typeof value !== 'string') { if (typeof value !== "string") {
return false; return false;
} }
const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; const urlRegex =
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
return urlRegex.test(value); return urlRegex.test(value);
} }
}
}

View File

@ -1,19 +1,17 @@
import { Field } from "@/utils/classes/Field"; import { Field } from "@/utils/classes/Field";
export class MultiselectFieldImpl extends Field { export class MultiselectFieldImpl extends Field {
tags: Set<any>; tags: Set<any>;
selectedTags: Set<any>; selectedTags: Set<any>;
constructor(title: string, tags: Set<any> = new Set()) { constructor(title: string, tags: Set<any> = new Set()) {
super('MultiselectTableIcon', title); super("MultiselectTableIcon", title);
this.tags = tags this.tags = tags;
this.selectedTags = new Set(); this.selectedTags = new Set();
} }
getTags() { getTags() {
return this.tags return this.tags;
} }
addTag(tag: any) { addTag(tag: any) {
@ -21,7 +19,7 @@ export class MultiselectFieldImpl extends Field {
} }
removeTag(tag: any) { removeTag(tag: any) {
if (this.tags.has(tag)){ if (this.tags.has(tag)) {
this.tags.delete(tag); this.tags.delete(tag);
} }
} }
@ -31,9 +29,8 @@ export class MultiselectFieldImpl extends Field {
} }
removeSelectedTag(tag: any) { removeSelectedTag(tag: any) {
if (this.selectedTags.has(tag)){ if (this.selectedTags.has(tag)) {
this.selectedTags.delete(tag); this.selectedTags.delete(tag);
} }
} }
}
}

View File

@ -1,15 +1,12 @@
import { Icons } from "@/utils/constants"; import { Icons } from "@/utils/constants";
import { Field } from "@/utils/classes/Field"; import { Field } from "@/utils/classes/Field";
export class StringFieldImpl extends Field { export class StringFieldImpl extends Field {
constructor(title: string, iconKey: keyof typeof Icons = "TextTableIcon") { constructor(title: string, iconKey: keyof typeof Icons = "TextTableIcon") {
super(iconKey, title); super(iconKey, title);
} }
validateInput(value: any) : boolean { validateInput(value: any): boolean {
return typeof value === 'string'; return typeof value === "string";
} }
}
}

View File

@ -1,15 +1,43 @@
import { mockFetchResources, mockFetchServices, mockFetchUsers } from "../functions/mockFetch"; import {
mockFetchResources,
mockFetchServices,
mockFetchUsers,
} from "../functions/mockFetch";
import { CollectionDataImpl } from "../implementations/CollectionDataImpl"; import { CollectionDataImpl } from "../implementations/CollectionDataImpl";
import { CollectionImpl } from "../implementations/CollectionImpl"; import { CollectionImpl } from "../implementations/CollectionImpl";
import { ResourceCollectionDataType, ServiceCollectionDataType, UserCollectionDataType } from "./CollectionDataType"; import {
ResourceCollectionDataType,
ServiceCollectionDataType,
UserCollectionDataType,
} from "./CollectionDataType";
const ServiceCollectionData = new CollectionDataImpl(ServiceCollectionDataType, mockFetchServices()); const ServiceCollectionData = new CollectionDataImpl(
const ResourceCollectionData = new CollectionDataImpl(ResourceCollectionDataType, mockFetchResources()); ServiceCollectionDataType,
const UserCollectionData = new CollectionDataImpl(UserCollectionDataType, mockFetchUsers()); mockFetchServices()
);
const ResourceCollectionData = new CollectionDataImpl(
ResourceCollectionDataType,
mockFetchResources()
);
const UserCollectionData = new CollectionDataImpl(
UserCollectionDataType,
mockFetchUsers()
);
export const ServiceCollection = new CollectionImpl('Service','ServiceIcon',new CollectionDataImpl(ServiceCollectionDataType)) export const ServiceCollection = new CollectionImpl(
"Service",
"ServiceIcon",
new CollectionDataImpl(ServiceCollectionDataType)
);
export const ResourceCollection = new CollectionImpl('Resource','ResourceIcon',new CollectionDataImpl(ResourceCollectionDataType)) export const ResourceCollection = new CollectionImpl(
"Resource",
export const UserCollection = new CollectionImpl('User','UserIcon',new CollectionDataImpl(UserCollectionDataType)) "ResourceIcon",
new CollectionDataImpl(ResourceCollectionDataType)
);
export const UserCollection = new CollectionImpl(
"User",
"UserIcon",
new CollectionDataImpl(UserCollectionDataType)
);

View File

@ -6,21 +6,52 @@ import { LinkFieldImpl } from "../implementations/FieldImpl/LinkFieldImpl";
import { MultiselectFieldImpl } from "../implementations/FieldImpl/MultiselectFieldImpl"; import { MultiselectFieldImpl } from "../implementations/FieldImpl/MultiselectFieldImpl";
import { StringFieldImpl } from "../implementations/FieldImpl/StringFieldImpl"; import { StringFieldImpl } from "../implementations/FieldImpl/StringFieldImpl";
const programSet: Set<PROGRAM> = new Set([PROGRAM.COMMUNITY_EDUCATION, PROGRAM.DOMESTIC_VIOLENCE, PROGRAM.ECONOMIC_STABILITY]); const programSet: Set<PROGRAM> = new Set([
PROGRAM.COMMUNITY_EDUCATION,
PROGRAM.DOMESTIC_VIOLENCE,
PROGRAM.ECONOMIC_STABILITY,
]);
const program = new MultiselectFieldImpl("program", programSet); const program = new MultiselectFieldImpl("program", programSet);
const requirements = new StringFieldImpl("requirements","RequirementsTableIcon"); const requirements = new StringFieldImpl(
"requirements",
"RequirementsTableIcon"
);
const name = new StringFieldImpl("name"); const name = new StringFieldImpl("name");
const summary = new StringFieldImpl("summary"); const summary = new StringFieldImpl("summary");
const statusSet: Set<STATUS> = new Set([STATUS.ACCEPTING_CLIENTS, STATUS.CLOSED, STATUS.FULL]) const statusSet: Set<STATUS> = new Set([
STATUS.ACCEPTING_CLIENTS,
STATUS.CLOSED,
STATUS.FULL,
]);
const status = new MultiselectFieldImpl("status", statusSet); const status = new MultiselectFieldImpl("status", statusSet);
const link = new LinkFieldImpl() const link = new LinkFieldImpl();
const tags = new MultiselectFieldImpl("tags") const tags = new MultiselectFieldImpl("tags");
const roleSet: Set<USER> = new Set([USER.ADMIN, USER.EMPLOYEE, USER.VOLUNTEER]); const roleSet: Set<USER> = new Set([USER.ADMIN, USER.EMPLOYEE, USER.VOLUNTEER]);
const role = new MultiselectFieldImpl("role", roleSet) const role = new MultiselectFieldImpl("role", roleSet);
const experience = new IntegerFieldImpl("yoe") const experience = new IntegerFieldImpl("yoe");
const email = new EmailFieldImpl() const email = new EmailFieldImpl();
const group = new StringFieldImpl("group") const group = new StringFieldImpl("group");
export const ServiceCollectionDataType: Field[] = [name, status, summary, requirements, program, tags] export const ServiceCollectionDataType: Field[] = [
export const ResourceCollectionDataType: Field[] = [name, summary, link, program, tags] name,
export const UserCollectionDataType: Field[] = [name, role, email, program, experience, group] status,
summary,
requirements,
program,
tags,
];
export const ResourceCollectionDataType: Field[] = [
name,
summary,
link,
program,
tags,
];
export const UserCollectionDataType: Field[] = [
name,
role,
email,
program,
experience,
group,
];