new form for login

This commit is contained in:
ameer2468
2024-07-24 18:40:56 +03:00
parent 1fecf6d034
commit 2d41c47a92
4 changed files with 300 additions and 37 deletions

View File

@@ -138,7 +138,7 @@ function Tabs() {
else if (e.button === 1) removeTab(index);
}}
className={clsx(
'group relative flex h-full min-w-40 shrink-0 flex-row items-center justify-center px-8 text-center duration-[50ms]',
'duration-[50ms] group relative flex h-full min-w-40 shrink-0 flex-row items-center justify-center px-8 text-center',
ctx.tabIndex === index
? 'text-ink'
: 'top-bar-blur border-t border-sidebar-divider bg-sidebar/30 text-ink-faint/60 transition-colors hover:bg-app/50'
@@ -166,7 +166,7 @@ function Tabs() {
<Tooltip keybinds={[keybind.icon, 'T']} label={t('new_tab')}>
<button
onClick={addTab}
className="flex flex-row items-center justify-center rounded p-1.5 transition-colors duration-[50ms] hover:bg-app/80"
className="duration-[50ms] flex flex-row items-center justify-center rounded p-1.5 transition-colors hover:bg-app/80"
>
<Plus weight="bold" size={14} />
</button>

View File

@@ -0,0 +1,257 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useZodForm } from '@sd/client';
import { Button, Card, Divider, Form, Input, Tooltip, z } from '@sd/ui';
import { motion } from 'framer-motion';
import { useState } from 'react';
import { GoogleLogo, Icon } from '@phosphor-icons/react';
import { Apple, Github } from '@sd/assets/svgs/brands';
import clsx from 'clsx';
import { Controller, useForm } from 'react-hook-form';
const Tabs = ['Login', 'Register'] as const;
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
})
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
confirmPassword: z.string().min(6),
}).refine(data => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword']
})
type RegisterData = z.infer<typeof RegisterSchema>
type SocialLogin = {
name: "Github" | "Google" | "Apple";
icon: Icon;
}
const SocialLogins: SocialLogin[] = [
{name: 'Github', icon: Github},
{name: 'Google', icon: GoogleLogo},
{name: 'Apple', icon: Apple},
]
const LoginRegister = () => {
const [activeTab, setActiveTab] = useState<'Login' | 'Register'>('Login');
const socialLoginHandlers = (name: SocialLogin['name']) => {
return {
'Github': () => {
console.log('Github login');
},
'Google': () => {
console.log('Google login');
},
'Apple': () => {
console.log('Apple login');
}
}[name]();
}
return (
<Card className="relative flex w-full max-w-[320px] flex-col items-center justify-center !p-0">
<div className='flex w-full'>
{Tabs.map((text) => (
<div key={text} onClick={() => {
setActiveTab(text)
}} className={clsx("relative flex-1 border-b border-app-line p-2.5 text-center",
text === 'Login' ? 'rounded-tl-md' : 'rounded-tr-md',
)}>
<p className={clsx('relative z-10 text-sm transition-colors duration-200',
text === activeTab ? 'font-medium text-ink' : 'text-ink-faint'
)}>{text}</p>
{text === activeTab && (
<motion.div
animate={{
borderRadius: text === 'Login' ? '0.3rem 0 0 0' : '0 0.3rem 0 0',
}}
layoutId='tab' className={clsx("absolute inset-x-0 top-0 z-0 size-full bg-app-line/60"
)} />
)}
</div>
))}
</div>
<div className='flex w-full flex-col justify-center gap-1.5 p-5'>
{activeTab === 'Login' ? <Login/> : <Register/>}
<div className='my-2 flex w-full items-center gap-3'>
<Divider/>
<p className='text-xs text-ink-faint'>OR</p>
<Divider/>
</div>
<div className='flex justify-center gap-3'>
{SocialLogins.map((social) => (
<Tooltip key={social.name} label={social.name} position='bottom'>
<div onClick={() => socialLoginHandlers(social.name)} key={social.name} className='rounded-full border border-app-line bg-app-input p-3'>
<social.icon style={{
fill: 'white'
}} weight='bold' className='size-4'/>
</div>
</Tooltip>
))}
</div>
</div>
</Card>
)
}
const Register = () => {
// useZodForm seems to be out-dated or needs
//fixing as it does not support the schema using zod.refine
const form = useForm<RegisterData>(
{
resolver: zodResolver(RegisterSchema),
defaultValues: {
email: '',
password: '',
confirmPassword: '',
}
})
return (
<Form
onSubmit={form.handleSubmit((data) => {
// handle login submission
return console.log(data);
})}
form={form}
>
<div className='flex flex-col gap-1.5'>
<Controller
control={form.control}
name="email"
render={({ field }) => (
<Input
{...field}
placeholder="Email"
error={Boolean(form.formState.errors.email?.message)}
type="email"
disabled={form.formState.isSubmitting}
/>
)}
/>
{form.formState.errors.email && (
<p className="text-xs text-red-500">{form.formState.errors.email.message}</p>
)}
<Controller
control={form.control}
name="password"
render={({ field }) => (
<Input
{...field}
placeholder="Password"
error={Boolean(form.formState.errors.password?.message)}
type="password"
className='w-full'
disabled={form.formState.isSubmitting}
/>
)}
/>
{form.formState.errors.password && (
<p className="text-xs text-red-500">{form.formState.errors.password.message}</p>
)}
<Controller
control={form.control}
name="confirmPassword"
render={({ field }) => (
<Input
{...field}
placeholder="Confirm Password"
error={Boolean(form.formState.errors.confirmPassword?.message)}
type="password"
className='w-full'
disabled={form.formState.isSubmitting}
/>
)}
/>
{form.formState.errors.confirmPassword && (
<p className="text-xs text-red-500">{form.formState.errors.confirmPassword.message}</p>
)}
<Button
type="submit"
className='mx-auto mt-2 w-full'
variant="accent"
onClick={form.handleSubmit((data) => {
console.log(data);
})}
disabled={form.formState.isSubmitting}
>
Submit
</Button>
</div>
</Form>
)
}
const Login = () => {
const form = useZodForm(
{
schema: LoginSchema,
defaultValues: {
email: '',
password: '',
}
})
return (
<Form
onSubmit={form.handleSubmit((data) => {
// handle login submission
console.log(data);
})}
form={form}
>
<div className='flex flex-col gap-1.5'>
<Controller
control={form.control}
name="email"
render={({ field }) => (
<Input
{...field}
placeholder="Email"
error={Boolean(form.formState.errors.email?.message)}
type="email"
disabled={form.formState.isSubmitting}
/>
)}
/>
{form.formState.errors.email && (
<p className="text-xs text-red-500">{form.formState.errors.email.message}</p>
)}
<Controller
control={form.control}
name="password"
render={({ field }) => (
<Input
{...field}
placeholder="Password"
error={Boolean(form.formState.errors.password?.message)}
type="password"
className='w-full'
disabled={form.formState.isSubmitting}
/>
)}
/>
{form.formState.errors.password && (
<p className="text-xs text-red-500">{form.formState.errors.password.message}</p>
)}
<Button
type="submit"
className='mx-auto mt-2 w-full'
variant="accent"
onClick={form.handleSubmit((data) => {
console.log(data);
})}
disabled={form.formState.isSubmitting}
>
Submit
</Button>
</div>
</Form>
)
}
export default LoginRegister;

View File

@@ -0,0 +1,31 @@
import { Envelope } from "@phosphor-icons/react";
import { Card } from '@sd/ui';
import { TruncatedText } from "~/components";
import { AuthRequiredOverlay } from "~/components/AuthRequiredOverlay";
const Profile = ({ email, authStore }: { email?: string; authStore: { status: string } }) => {
const emailName = authStore.status === 'loggedIn' ? email?.split('@')[0] : 'guest user';
return (
<Card className="relative flex w-full flex-col items-center justify-center !p-0 lg:max-w-[320px]">
<AuthRequiredOverlay />
<div className='p-3'>
<h1 className="mx-auto mt-3 text-lg">
Welcome <span className="font-bold">{emailName},</span>
</h1>
<div className="mx-auto mt-4 flex w-full flex-col gap-2">
<Card className="w-full items-center justify-start gap-1 bg-app-input !px-2">
<div className="w-[20px]">
<Envelope weight="fill" width={20} />
</div>
<TruncatedText>
{authStore.status === 'loggedIn' ? email : 'guestuser@outlook.com'}
</TruncatedText>
</Card>
</div>
</div>
</Card>
);
};
export default Profile;

View File

@@ -1,12 +1,11 @@
import { Envelope, User } from '@phosphor-icons/react';
import { useEffect, useState } from 'react';
import { auth, useBridgeMutation, useBridgeQuery, useFeatureFlag } from '@sd/client';
import { Button, Card, Input, toast } from '@sd/ui';
import { TruncatedText } from '~/components';
import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay';
import { Button, Input, toast } from '@sd/ui';
import { useEffect, useState } from 'react';
import { useLocale } from '~/hooks';
import { Heading } from '../Layout';
import { Heading } from '../../Layout';
import LoginRegister from './LoginRegister';
import Profile from './Profile';
export const Component = () => {
const { t } = useLocale();
@@ -30,41 +29,17 @@ export const Component = () => {
description={t('spacedrive_cloud_description')}
/>
<div className="flex flex-col justify-between gap-5 lg:flex-row">
<Profile authStore={authStore} email={me.data?.email} />
{authStore.status === 'notLoggedIn' ? (
<LoginRegister/>
) : (
<Profile authStore={authStore} email={me.data?.email} />
)}
</div>
{useFeatureFlag('hostedLocations') && <HostedLocationsPlayground />}
</>
);
};
const Profile = ({ email, authStore }: { email?: string; authStore: { status: string } }) => {
const emailName = authStore.status === 'loggedIn' ? email?.split('@')[0] : 'guest user';
return (
<Card className="relative flex w-full flex-col items-center justify-center !p-6 lg:max-w-[320px]">
<AuthRequiredOverlay />
<div
className="flex size-[90px] items-center justify-center rounded-full
border border-app-line bg-app-input"
>
<User weight="fill" className="mx-auto text-4xl text-ink-faint" />
</div>
<h1 className="mx-auto mt-3 text-lg">
Welcome <span className="font-bold">{emailName},</span>
</h1>
<div className="mx-auto mt-4 flex w-full flex-col gap-2">
<Card className="w-full items-center justify-start gap-1 bg-app-input !px-2">
<div className="w-[20px]">
<Envelope weight="fill" width={20} />
</div>
<TruncatedText>
{authStore.status === 'loggedIn' ? email : 'guestuser@outlook.com'}
</TruncatedText>
</Card>
</div>
</Card>
);
};
function HostedLocationsPlayground() {
const locations = useBridgeQuery(['cloud.locations.list'], { retry: false });