mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-09 14:00:15 -04:00
Add prettier and reformat project
This commit is contained in:
parent
3fd0a545e7
commit
f2765ecb47
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"extends": "next/core-web-vitals"
|
"extends": "next/core-web-vitals"
|
||||||
}
|
}
|
||||||
|
|
6
compass/.prettierrc
Normal file
6
compass/.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
26
compass/app/auth/actions.ts
Normal file
26
compass/app/auth/actions.ts
Normal 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");
|
||||||
|
}
|
3
compass/app/auth/error/page.tsx
Normal file
3
compass/app/auth/error/page.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function ErrorPage() {
|
||||||
|
return <p>Sorry, something went wrong</p>;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
© 2024 Compass Center
|
© 2024 Compass Center
|
||||||
</p>
|
</p>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
);
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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">
|
>
|
||||||
© 2024 Compass Center
|
<p>{error}</p>
|
||||||
</p>
|
</div>
|
||||||
</Paper>
|
</div>
|
||||||
</>
|
</form>
|
||||||
);
|
<p className="text-center mt-6 text-gray-500 text-xs">
|
||||||
|
© 2024 Compass Center
|
||||||
|
</p>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -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>
|
};
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
9484
compass/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,4 @@ export class CollectionImpl {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user