Skip to content

Commit

Permalink
made user login and new user forms (#41)
Browse files Browse the repository at this point in the history
* made user login and new user forms

* Update new user/login form with UX tweaks

* Move account items to user menu in navbar

* Whoop typo

* Fix form popup animation

* Organize form imports

---------

Co-authored-by: Thomas Shaw <[email protected]>
  • Loading branch information
debbylin02 and printer83mph authored Apr 16, 2024
1 parent 8cc712c commit 4b6cb81
Show file tree
Hide file tree
Showing 12 changed files with 332 additions and 15 deletions.
6 changes: 6 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions frontend/src/renderer/src/components/forms/new-asset-form.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Controller, SubmitHandler, useFieldArray, useForm } from 'react-hook-form';
import { BiImageAdd } from 'react-icons/bi';
import { MdCheck, MdDelete } from 'react-icons/md';

import { useAssetsSearchRefetch } from '@renderer/hooks/use-assets-search';
import useDownloads from '@renderer/hooks/use-downloads';
import fetchClient from '@renderer/lib/fetch-client';
import { encodeThumbnailImage } from '@renderer/lib/image-util';
import { Asset } from '@renderer/types';
import TextInput from '../input/text-input';
import KeywordsInput from '../input/keywords-input';
import Label from '../input/label';
import { BiImageAdd } from 'react-icons/bi';
import { MdCheck, MdDelete } from 'react-icons/md';
import TextInput from '../input/text-input';

export interface NewAssetFormData {
assetName: string;
Expand Down
144 changes: 144 additions & 0 deletions frontend/src/renderer/src/components/forms/new-user-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { AnimatePresence, motion } from 'framer-motion';
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
import { Link } from 'react-router-dom';

// import { useSelectedAsset } from '@renderer/hooks/use-asset-select';
// import useDownloads from '@renderer/hooks/use-downloads';
import Label from '../input/label';
import TextInput from '../input/text-input';

export interface NewUserFormData {
firstName: string; // first name
lastName: string; // last name
pennkey: string; // username
school: 'sas' | 'seas' | 'wharton';
password: string; // password
}

interface NewUserFormProps {
afterSubmit?: SubmitHandler<NewUserFormData>;
}

// POST to /api/v1/assets/{uuid}/versions - Upload new version for a given asset
export default function NewUserForm({ afterSubmit }: NewUserFormProps) {
// const { commitChanges } = useDownloads();
// const { mutate: mutateSelectedAsset } = useSelectedAsset();

const {
register,
handleSubmit,
formState: { isSubmitting },
control,
} = useForm<NewUserFormData>({
defaultValues: {
firstName: '',
lastName: '',
pennkey: '',
school: 'seas',
password: '',
},
});

// --------------------------------------------

const submitHandler = async (data: NewUserFormData) => {
// const { versions: downloadedVersions } = await window.api.ipc('assets:list-downloaded', null);
// const downloaded = downloadedVersions.find(({ asset_id }) => asset_id === uuid);

// if (!downloaded) {
// console.error('No downloaded version found for asset', uuid);
// return;
// }

// // Calling fetchClient.POST()
// await commitChanges({
// asset_id: uuid,
// semver: downloaded.semver,
// message: data.message,
// is_major: data.is_major,
// });

// // refetch selected asset in case it's the one we updated
// mutateSelectedAsset();

// Combine assetFiles from state with form data
if (afterSubmit) afterSubmit(data); // Call the onSubmit function provided by props
};

const [pennkey, school] = useWatch({ control, name: ['pennkey', 'school'] });
const computedEmail = pennkey ? `${pennkey}@${school}.upenn.edu` : undefined;

return (
<form onSubmit={handleSubmit(submitHandler)}>
<div className="flex flex-col gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<TextInput
label="First Name"
placeholder="Ben"
{...register('firstName', { required: true })}
/>

<TextInput
label="Last Name"
placeholder="Franklin"
{...register('lastName', { required: true })}
/>
</div>

<div>
<div className="grid gap-4 sm:grid-cols-2">
<TextInput
label="Pennkey"
placeholder="benfranklin"
{...register('pennkey', { required: true })}
/>

<label {...register('school')}>
<Label label="School" />
<select className="select select-bordered w-full" {...register('school')}>
<option value="sas">CAS</option>
<option value="seas">SEAS</option>
<option value="wharton">Wharton</option>
</select>
</label>
</div>
<AnimatePresence initial={false}>
{computedEmail && (
<motion.div
className="overflow-hidden text-center"
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
>
<div className="mt-4 select-none text-base-content/50">
Email: <span className="text-base-content/100">{computedEmail}</span>
</div>
</motion.div>
)}
</AnimatePresence>
</div>

<TextInput
label="Password"
placeholder="••••••••"
type="password"
{...register('password', { required: true })}
/>

<div className="mt-6 flex w-full justify-center">
<button type="submit" className="btn btn-primary btn-wide" disabled={isSubmitting}>
Create Account
{isSubmitting && <span className="loading loading-spinner ml-2" />}
</button>
</div>

<Link
to="/user-login"
className="mx-auto text-center text-primary/80 underline hover:text-primary/100"
>
Already have an account? Log in here.
</Link>
</div>
</form>
);
}
91 changes: 91 additions & 0 deletions frontend/src/renderer/src/components/forms/user-login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { SubmitHandler, useForm } from 'react-hook-form';
import { Link } from 'react-router-dom';

// import { useSelectedAsset } from '@renderer/hooks/use-asset-select';
// import useDownloads from '@renderer/hooks/use-downloads';
import TextInput from '../input/text-input';

export interface UserLoginFormData {
username: string; // username
password: string; // password
}

interface UserLoginFormProps {
afterSubmit?: SubmitHandler<UserLoginFormData>;
}

// POST to /api/v1/assets/{uuid}/versions - Upload new version for a given asset
export default function UserLoginForm({ afterSubmit }: UserLoginFormProps) {
// const { commitChanges } = useDownloads();
// const { mutate: mutateSelectedAsset } = useSelectedAsset();

const {
register,
handleSubmit,
formState: { isSubmitting },
} = useForm<UserLoginFormData>({
defaultValues: {
username: '',
password: '',
},
});

// --------------------------------------------

const submitHandler = async (data: UserLoginFormData) => {
// const { versions: downloadedVersions } = await window.api.ipc('assets:list-downloaded', null);
// const downloaded = downloadedVersions.find(({ asset_id }) => asset_id === uuid);

// if (!downloaded) {
// console.error('No downloaded version found for asset', uuid);
// return;
// }

// // Calling fetchClient.POST()
// await commitChanges({
// asset_id: uuid,
// semver: downloaded.semver,
// message: data.message,
// is_major: data.is_major,
// });

// // refetch selected asset in case it's the one we updated
// mutateSelectedAsset();

// Combine assetFiles from state with form data
if (afterSubmit) afterSubmit(data); // Call the onSubmit function provided by props
};

return (
<form onSubmit={handleSubmit(submitHandler)}>
<div className="flex flex-col gap-4">
<TextInput
label="Pennkey"
placeholder="benfranklin"
{...register('username', { required: true })}
/>

<TextInput
label="Password"
placeholder="••••••••"
type="password"
{...register('password', { required: true })}
/>

<div className="mt-6 flex w-full justify-center">
<button type="submit" className="btn btn-primary btn-wide" disabled={isSubmitting}>
Login
{isSubmitting && <span className="loading loading-spinner ml-2" />}
</button>
</div>

<Link
to="/new-user"
className="mx-auto text-center text-primary/80 underline hover:text-primary/100"
>
Don&apos;t have a Griddle account? Create one here.
</Link>
</div>
</form>
);
}
11 changes: 6 additions & 5 deletions frontend/src/renderer/src/components/layout/form-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ export default function FormPopup({
return (
<>
<motion.div
className="absolute inset-0 z-10 bg-black/20"
className="absolute inset-0 z-10 bg-black/30"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.25 }}
/>
<motion.div
className="absolute inset-0 z-10 overflow-y-auto"
className="absolute inset-0 z-10 origin-top overflow-y-auto"
onClick={onClose}
initial={{ y: 30, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ ease: 'easeOut' }}
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ ease: 'easeOut', duration: 0.25 }}
>
<div
className="mx-auto my-6 w-full max-w-xl rounded-box bg-base-100 px-6 py-4 shadow-lg"
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/renderer/src/components/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { themeChange } from 'theme-change';
import ThemeSelector from './theme-selector';

import { CiSearch } from 'react-icons/ci';
import UserDropdown from './user-dropdown';

const Navbar = () => {
const [selectedTheme, setSelectedTheme] = useState('light'); // default theme
Expand Down Expand Up @@ -35,11 +36,6 @@ const Navbar = () => {
+ New Asset
</Link>

{/* Update Asset Button */}
{/* <Link className="btn btn-outline" to={'/update-asset'}>
+ Update Asset
</Link> */}

{/* Search Bar */}
<div className="form-control relative w-1/3">
<CiSearch className="absolute left-3 top-1/2 h-6 w-6 -translate-y-1/2 transform text-gray-500" />
Expand All @@ -58,6 +54,9 @@ const Navbar = () => {

<ThemeSelector selectedTheme={selectedTheme} setSelectedTheme={setSelectedTheme} />

{/* User info (login, signup, settings) */}
<UserDropdown />

{/* Placeholder for Right-Side Content */}
<div>{/* Content such as profile menu or additional buttons could go here */}</div>
</nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ const ThemeSelector = ({ selectedTheme, setSelectedTheme }) => {

return (
<div className="dropdown dropdown-end">
<label tabIndex={0} className="btn m-1 flex items-center justify-center gap-2">
<label tabIndex={0} className="btn btn-ghost m-1 flex items-center justify-center gap-2">
<MdPalette className="h-6 w-6" /> {/* Using the icon here */}
Themes
</label>
<ul tabIndex={0} className="menu dropdown-content w-52 rounded-box bg-base-100 p-2 shadow">
{themes.map((item, index) => (
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/renderer/src/components/layout/user-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MdLogin, MdPerson } from 'react-icons/md';
import { Link } from 'react-router-dom';

const UserDropdown = () => {
return (
<div className="dropdown dropdown-end">
<div tabIndex={0} className="btn btn-circle m-1 flex items-center justify-center gap-2">
<MdPerson className="h-6 w-6" /> {/* Using the icon here */}
</div>
<ul tabIndex={0} className="menu dropdown-content w-52 rounded-box bg-base-100 p-2 shadow">
<li className="">
<Link to="/user-login">
<MdLogin /> Login to Griddle
</Link>
</li>
</ul>
</div>
);
};

export default UserDropdown;
10 changes: 10 additions & 0 deletions frontend/src/renderer/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import HomeView from './routes/home-view';
import { RouterProvider, createHashRouter } from 'react-router-dom';
import NewAssetView from './routes/new-asset-view';
import UpdateAssetView from './routes/update-asset-view';
import UserLoginView from './routes/user-login-view';
import NewUserView from './routes/new-user-view';

const router = createHashRouter([
{
Expand All @@ -20,6 +22,14 @@ const router = createHashRouter([
path: 'update-asset',
element: <UpdateAssetView />,
},
{
path: 'user-login',
element: <UserLoginView />,
},
{
path: 'new-user',
element: <NewUserView />,
},
],
},
]);
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/renderer/src/routes/new-user-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useNavigate } from 'react-router-dom';

import NewUserForm from '@renderer/components/forms/new-user-form';
import FormPopup from '@renderer/components/layout/form-popup';
// import { useAssetSelectStore } from '@renderer/hooks/use-asset-select';

export default function NewUserView() {
const navigate = useNavigate();
// const setSelectedId = useAssetSelectStore((state) => state.setSelected);

return (
<FormPopup title="Create your Griddle Account" onClose={() => navigate('/')}>
<NewUserForm
afterSubmit={() => {
navigate('/');
}}
/>
</FormPopup>
);
}
Loading

0 comments on commit 4b6cb81

Please sign in to comment.