Merge branch 'main' into backend-emmafoster-GEN-95-entities

This commit is contained in:
emmalynf 2024-03-25 20:03:34 -04:00
commit 64aa4b5bae
28 changed files with 844 additions and 232 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

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

View File

@ -0,0 +1,22 @@
import Paper from '@/components/auth/Paper';
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<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">
{children}
</form>
<p className="text-center mt-6 text-gray-500 text-xs">
&copy; 2024 Compass Center
</p>
</Paper>
)
}

View File

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

@ -0,0 +1,62 @@
// pages/index.tsx
"use client";
import { useState, useEffect } from 'react';
import Button from '@/components/Button';
import PasswordInput from '@/components/auth/PasswordInput';
import ErrorBanner from '@/components/auth/ErrorBanner';
function isStrongPassword(password: string): boolean {
const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;
return strongPasswordRegex.test(password);
}
export default function Page() {
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isButtonDisabled, setIsButtonDisabled] = useState(true);
useEffect(() => {
setIsButtonDisabled(newPassword === '' || confirmPassword === '' || newPassword !== confirmPassword|| !isStrongPassword(newPassword));
}, [newPassword, confirmPassword]);
return (
<>
<div className="text-center sm:text-left">
<h1 className="font-bold text-xl text-purple-800">New Password</h1>
</div>
<div className="mb-4">
<PasswordInput
title="Enter New Password"
value={newPassword}
valid={!isButtonDisabled || isStrongPassword(newPassword)}
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 className="mb-6">
<PasswordInput
title="Confirm Password"
value={confirmPassword}
valid={!isButtonDisabled || (newPassword === confirmPassword && confirmPassword !== '')}
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,9 +1,5 @@
import '../styles/globals.css';
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Login',
}
export default function RootLayout({
// Layouts must accept a children prop.

View File

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

View File

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

View File

@ -0,0 +1,39 @@
"use client"
import Callout from "@/components/resource/Callout";
import Card from "@/components/resource/Card";
import { LandingSearchBar } from "@/components/resource/LandingSearchBar";
import { BookOpenIcon, BookmarkIcon, ClipboardIcon } from "@heroicons/react/24/solid";
import Image from 'next/image';
export default function Page() {
return (
<div className="min-h-screen flex flex-col">
{/* icon + title */}
<div className="pt-16 px-8 pb-4 flex-grow">
<div className="mb-4 flex items-center space-x-4">
<Image
src="/logo.png"
alt='Compass Center logo.'
width={25}
height={25}
/>
<h1 className='font-bold text-2xl text-purple-800'>Compass Center Advocate Landing Page</h1>
</div>
<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.
<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>
</div>
<div className="p-8 flex-grow border-t border-gray-200 bg-gray-50">
{/* link to different pages */}
<div className="grid grid-cols-3 gap-6 pb-6">
<Card icon={<BookmarkIcon />} text="Resources" />
<Card icon={<ClipboardIcon />} text="Services" />
<Card icon={<BookOpenIcon />} text="Training Manuals" />
</div>
{/* search bar */}
<LandingSearchBar />
</div>
</div>
)
}

View File

@ -1,23 +1,26 @@
import { FunctionComponent, ReactNode } from 'react';
type ButtonProps = {
children: ReactNode;
onClick?: () => void; // make the onClick handler optional
};
const Button: FunctionComponent<ButtonProps> = ({ children, onClick }) => {
return (
<button
// className="px-4 py-2 font-bold text-white bg-purple-600 rounded hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-opacity-var focus:ring-color-var"
className="inline-block rounded border border-purple-600 bg-purple-600 px-12 py-3 text-sm font-semibold text-white hover:bg-transparent hover:text-purple-600 focus:outline-none focus:ring active:text-purple-500"
onClick={onClick}
// style={{
// '--ring-opacity-var': `var(--ring-opacity)`,
// '--ring-color-var': `rgba(var(--ring-color), var(--ring-opacity))`
// }}
>
{children}
</button>
);
};
export default Button;
import { FunctionComponent, ReactNode } from 'react';
type ButtonProps = {
children: ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
type?: "button" | "submit" | "reset"; // specify possible values for type
disabled?: boolean;
};
const Button: FunctionComponent<ButtonProps> = ({ children, 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 (
<button
className={buttonClassName}
onClick={onClick}
type={type}
disabled={disabled}
>
{children}
</button>
);
};
export default Button;

View File

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

View File

@ -1,41 +1,36 @@
import { Icons } from '@/utils/constants';
import React, { FunctionComponent, InputHTMLAttributes, ReactElement, ReactNode } from 'react';
import React, { FunctionComponent, InputHTMLAttributes, ReactNode, ChangeEvent } from 'react';
type InputProps = InputHTMLAttributes<HTMLInputElement> & {
iconKey?: keyof typeof Icons; // Use keyof typeof to ensure the key exists in Icons
title?: string; // Assuming title is always a string
type?: string;
placeholder?: string;
icon?: ReactNode;
title?: ReactNode;
type?:ReactNode;
placeholder?:ReactNode
valid?:boolean;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
};
const Input: FunctionComponent<InputProps> = ({ iconKey, type, title, placeholder, ...rest }) => {
const IconComponent = iconKey ? Icons[iconKey] : null;
const Input: FunctionComponent<InputProps> = ({ icon, type, title, placeholder, onChange, valid = true, ...rest }) => {
return (
<div className="mb-4">
{title && (
<div className="mb-1">
<label htmlFor={title} className="text-sm font-semibold text-gray-700">
{title}
</label>
</div>
)}
<div className="flex items-center border border-gray-300 rounded-md shadow-sm overflow-hidden">
{IconComponent && (
<span className="inline-flex items-center px-3 border-r border-gray-300 text-gray-500">
<IconComponent className="h-5 w-5" />
</span>
)}
<input
{...rest}
type={type}
id={title}
placeholder={placeholder}
className="w-full border-none p-3 text-sm focus:ring-0"
style={{ boxShadow: 'none' }} // This ensures that the input doesn't have an inner shadow
/>
</div>
</div>
<div>
<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"}
>
<span className="text-xs font-semibold text-gray-700"> {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>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react';
interface ErrorBannerProps {
heading: string;
description?: string | null;
}
const ErrorBanner: React.FC<ErrorBannerProps> = ({ heading, description = null }) => {
return (
<div role="alert" className="rounded border-s-4 border-red-500 bg-red-50 p-4">
<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>
);
};
export default ErrorBanner;

View File

@ -1,15 +1,15 @@
import React, { ReactNode } from 'react';
interface PageInterface {
children: ReactNode;
}
const Paper: React.FC<PageInterface> = ({ children }) => {
return (
<div className="w-full min-h-screen px-4 py-16 bg-gray-100 sm:px-6 lg:px-8">
{children}
</div>
);
};
import React, { ReactNode } from 'react';
interface PageInterface {
children: ReactNode;
}
const Paper: React.FC<PageInterface> = ({ children }) => {
return (
<div className="w-full min-h-screen px-4 py-16 bg-gray-50 sm:px-6 lg:px-8">
{children}
</div>
);
};
export default Paper;

View File

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

View File

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

View File

@ -0,0 +1,20 @@
import React, { ReactNode } from "react";
interface TagProps {
text: string;
icon: React.ReactNode;
}
const Card: React.FC<TagProps> = ({ text, icon }) => {
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">
<span className="h-5 text-purple-700 w-5">
{icon}
</span>
<span className="text-sm text-gray-800 font-semibold">{text}</span>
</div>
);
};
export default Card;

View File

@ -0,0 +1,48 @@
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid"
import React, { useState } from 'react';
import Image from 'next/image';
export const LandingSearchBar: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const clearSearch = () => {
setSearchTerm('');
};
return (
<div className="max-w mx-auto">
{/* searchbar */}
<div className="flex items-center bg-white border border-gray-200 shadow rounded-md">
<div className="flex-grow">
<input
className="sm:text-sm text-gray-800 w-full px-6 py-3 rounded-md focus:outline-none"
type="text"
placeholder="Search..."
value={searchTerm}
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>
);
};

View File

@ -0,0 +1,46 @@
import React from 'react';
import { HomeIcon, ChevronDoubleLeftIcon, BookmarkIcon, ClipboardIcon, BookOpenIcon } from '@heroicons/react/24/solid';
import { SidebarItem } from './SidebarItem';
import { UserProfile } from './UserProfile';
interface SidebarProps {
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
return (
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
{/* button to close sidebar */}
<div className="flex justify-end">
<button
onClick={() => setIsSidebarOpen(false)}
className="py-2 text-gray-500 hover:text-gray-800"
aria-label="Close sidebar"
>
<ChevronDoubleLeftIcon className="h-5 w-5" />
</button>
</div>
<div className="flex flex-col space-y-8">
{/* user + logout button */}
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
<UserProfile />
</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;

View File

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

View File

@ -0,0 +1,11 @@
export const UserProfile = () => {
return (
<div className="flex flex-col items-start space-y-2">
<div className="flex flex-col">
<span className="text-sm font-semibold text-gray-800">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>
)
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
compass/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,47 +1,96 @@
/* globals.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
:root {
/* Colors */
--ring-color: 199, 21, 133;
/* This is the RGB value for a purple color */
--ring-opacity: 0.5;
/* Shadows */
--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);
/* Borders */
--border-radius: 0.375rem;
/* 6px */
--border-width: 1px;
/* Spacing */
--spacing-px: 1px;
--spacing-2: 0.5rem;
/* 8px */
--spacing-3: 0.75rem;
/* 12px */
/* Font */
--font-color: #4a5568;
/* A shade of gray */
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url('/fonts/Inter-Regular.ttf') format('ttf'),
url('/fonts/Inter-Bold.ttf') format('ttf'),
url('/fonts/Inter-Black.ttf') format('ttf'),
url('/fonts/Inter-ExtraBold.ttf') format('ttf'),
url('/fonts/Inter-ExtraLight.ttf') format('ttf'),
url('/fonts/Inter-Medium.ttf') format('ttf'),
url('/fonts/Inter-SemiBold.ttf') format('ttf'),
url('/fonts/Inter-Thin.ttf') format('ttf');
}
/* globals.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
:root {
/* Colors */
--ring-color: 199, 21, 133;
/* This is the RGB value for a purple color */
--ring-opacity: 0.5;
/* Shadows */
--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);
/* Borders */
--border-radius: 0.375rem;
/* 6px */
--border-width: 1px;
/* Spacing */
--spacing-px: 1px;
--spacing-2: 0.5rem;
/* 8px */
--spacing-3: 0.75rem;
/* 12px */
/* Font */
--font-color: #4a5568;
/* A shade of gray */
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url('/fonts/Inter-Regular.ttf') format('truetype');
}
/* Inter-Bold */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
src: url('/fonts/Inter-Bold.ttf') format('truetype');
}
/* Inter-Black */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
src: url('/fonts/Inter-Black.ttf') format('truetype');
}
/* Inter-ExtraBold */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
src: url('/fonts/Inter-ExtraBold.ttf') format('truetype');
}
/* Inter-ExtraLight */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
src: url('/fonts/Inter-ExtraLight.ttf') format('truetype');
}
/* Inter-Medium */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
src: url('/fonts/Inter-Medium.ttf') format('truetype');
}
/* Inter-SemiBold */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
src: url('/fonts/Inter-SemiBold.ttf') format('truetype');
}
/* Inter-Thin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
src: url('/fonts/Inter-Thin.ttf') format('truetype');
}

View File

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

View File

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

View File

@ -0,0 +1,9 @@
import { InputHTMLAttributes } from "react";
import { Icons } from "../constants";
export type InputProps = InputHTMLAttributes<HTMLInputElement> & {
iconKey?: keyof typeof Icons; // Use keyof typeof to ensure the key exists in Icons
title?: string; // Assuming title is always a string
type?: string;
placeholder?: string;
};