-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(fe): refactor settings page (#2128)
* fix(fe): refactor save * fix(fe): refactor settings * chore(fe): change import path name * fix(fe): change navigation * chore(fe): fix typo * chore(fe): delete import react * chore(fe): delete import react * chore(fe): use cn
- Loading branch information
1 parent
2191e86
commit b22bca3
Showing
13 changed files
with
686 additions
and
368 deletions.
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
apps/frontend/app/(main)/settings/_components/ConfirmNavigation.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import type { Route } from 'next' | ||
import type { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime' | ||
import { useRouter } from 'next/navigation' | ||
import type { MutableRefObject } from 'react' | ||
import { useEffect } from 'react' | ||
import { toast } from 'sonner' | ||
|
||
// const beforeUnloadHandler = (event: BeforeUnloadEvent) => { | ||
// // Recommended | ||
// event.preventDefault() | ||
|
||
// // Included for legacy support, e.g. Chrome/Edge < 119 | ||
// event.returnValue = true | ||
// return true | ||
// } | ||
|
||
/** | ||
* Prompt the user with a confirmation dialog when they try to navigate away from the page. | ||
*/ | ||
export const useConfirmNavigation = ( | ||
bypassConfirmation: MutableRefObject<boolean>, | ||
updateNow: boolean | ||
) => { | ||
const router = useRouter() | ||
useEffect(() => { | ||
const originalPush = router.push | ||
const newPush = ( | ||
href: string, | ||
options?: NavigateOptions | undefined | ||
): void => { | ||
if (updateNow) { | ||
if (!bypassConfirmation.current) { | ||
toast.error('You must update your information') | ||
} else { | ||
originalPush(href as Route, options) | ||
} | ||
return | ||
} | ||
if (!bypassConfirmation.current) { | ||
const isConfirmed = window.confirm( | ||
'Are you sure you want to leave?\nYour changes have not been saved.\nIf you leave this page, all changes will be lost.\nDo you still want to proceed?' | ||
) | ||
if (isConfirmed) { | ||
originalPush(href as Route, options) | ||
} | ||
return | ||
} | ||
originalPush(href as Route, options) | ||
} | ||
router.push = newPush | ||
return () => { | ||
router.push = originalPush | ||
} | ||
}, [router, bypassConfirmation.current]) | ||
} |
93 changes: 93 additions & 0 deletions
93
apps/frontend/app/(main)/settings/_components/CurrentPwSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { Button } from '@/components/ui/button' | ||
import { Input } from '@/components/ui/input' | ||
import { cn } from '@/lib/utils' | ||
import invisible from '@/public/24_invisible.svg' | ||
import visible from '@/public/24_visible.svg' | ||
import type { SettingsFormat } from '@/types/type' | ||
import Image from 'next/image' | ||
import React from 'react' | ||
import type { FieldErrors, UseFormRegister } from 'react-hook-form' | ||
import { FaCheck } from 'react-icons/fa6' | ||
|
||
interface CurrentPwSectionProps { | ||
currentPassword: string | ||
isCheckButtonClicked: boolean | ||
isPasswordCorrect: boolean | ||
setPasswordShow: React.Dispatch<React.SetStateAction<boolean>> | ||
passwordShow: boolean | ||
checkPassword: () => Promise<void> | ||
register: UseFormRegister<SettingsFormat> | ||
errors: FieldErrors<SettingsFormat> | ||
updateNow: boolean | ||
} | ||
|
||
export default function CurrentPwSection({ | ||
currentPassword, | ||
isCheckButtonClicked, | ||
isPasswordCorrect, | ||
setPasswordShow, | ||
passwordShow, | ||
checkPassword, | ||
register, | ||
errors, | ||
updateNow | ||
}: CurrentPwSectionProps) { | ||
return ( | ||
<> | ||
<label className="-mb-4 mt-4 text-xs">Password</label> | ||
<div className="flex items-center gap-2"> | ||
<div className="relative w-full justify-between"> | ||
<Input | ||
type={passwordShow ? 'text' : 'password'} | ||
placeholder="Current password" | ||
{...register('currentPassword')} | ||
disabled={ | ||
updateNow ? true : isCheckButtonClicked && isPasswordCorrect | ||
} | ||
className={cn( | ||
'flex justify-stretch border-neutral-300 text-neutral-600 ring-0 placeholder:text-neutral-400 focus-visible:ring-0 disabled:bg-neutral-200 disabled:text-neutral-400', | ||
errors.currentPassword && 'border-red-500', | ||
isCheckButtonClicked && | ||
(isPasswordCorrect ? 'border-primary' : 'border-red-500') | ||
)} | ||
/> | ||
<span | ||
className="absolute right-0 top-0 flex h-full items-center p-3" | ||
onClick={() => setPasswordShow(!passwordShow)} | ||
> | ||
<Image | ||
src={passwordShow ? visible : invisible} | ||
alt={passwordShow ? 'visible' : 'invisible'} | ||
/> | ||
</span> | ||
</div> | ||
<Button | ||
disabled={!currentPassword} | ||
className="h-4/5 px-2 disabled:bg-neutral-400" | ||
onClick={() => { | ||
checkPassword() | ||
}} | ||
> | ||
<FaCheck size={20} /> | ||
</Button> | ||
</div> | ||
{errors.currentPassword && | ||
errors.currentPassword.message === 'Required' && ( | ||
<div className="-mt-4 inline-flex items-center text-xs text-red-500"> | ||
Required | ||
</div> | ||
)} | ||
{!errors.currentPassword && | ||
isCheckButtonClicked && | ||
(isPasswordCorrect ? ( | ||
<div className="text-primary -mt-4 inline-flex items-center text-xs"> | ||
Correct | ||
</div> | ||
) : ( | ||
<div className="-mt-4 inline-flex items-center text-xs text-red-500"> | ||
Incorrect | ||
</div> | ||
))} | ||
</> | ||
) | ||
} |
20 changes: 20 additions & 0 deletions
20
apps/frontend/app/(main)/settings/_components/IdSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Input } from '@/components/ui/input' | ||
|
||
export default function IdSection({ | ||
isLoading, | ||
defaultUsername | ||
}: { | ||
isLoading: boolean | ||
defaultUsername: string | ||
}) { | ||
return ( | ||
<> | ||
<label className="-mb-4 text-xs">ID</label> | ||
<Input | ||
placeholder={isLoading ? 'Loading...' : defaultUsername} | ||
disabled={true} | ||
className="border-neutral-300 text-neutral-600 placeholder:text-neutral-400 disabled:bg-neutral-200" | ||
/> | ||
</> | ||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
apps/frontend/app/(main)/settings/_components/LogoSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import codedangSymbol from '@/public/codedang-editor.svg' | ||
import Image from 'next/image' | ||
|
||
export default function LogoSection() { | ||
return ( | ||
<div | ||
className="flex h-svh max-h-[846px] w-full flex-col items-center justify-center gap-3 rounded-2xl" | ||
style={{ | ||
background: `var(--banner, | ||
linear-gradient(325deg, rgba(79, 86, 162, 0.00) 28.16%, rgba(79, 86, 162, 0.50) 93.68%), | ||
linear-gradient(90deg, #3D63B8 0%, #0E1322 100%) | ||
)` | ||
}} | ||
> | ||
<div className="flex items-center gap-3"> | ||
<Image src={codedangSymbol} alt="codedang" width={65} /> | ||
<p className="font-mono text-[40px] font-bold text-white">CODEDANG</p> | ||
</div> | ||
<p className="font-medium text-white">Online Judge Platform for SKKU</p> | ||
</div> | ||
) | ||
} |
107 changes: 107 additions & 0 deletions
107
apps/frontend/app/(main)/settings/_components/MajorSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Button } from '@/components/ui/button' | ||
import { | ||
Command, | ||
CommandInput, | ||
CommandGroup, | ||
CommandItem, | ||
CommandList, | ||
CommandEmpty | ||
} from '@/components/ui/command' | ||
import { | ||
Popover, | ||
PopoverTrigger, | ||
PopoverContent | ||
} from '@/components/ui/popover' | ||
import { ScrollArea } from '@/components/ui/scroll-area' | ||
import { majors } from '@/lib/constants' | ||
import { cn } from '@/lib/utils' | ||
import React from 'react' | ||
import { FaChevronDown, FaCheck } from 'react-icons/fa6' | ||
|
||
interface MajorSectionProps { | ||
majorOpen: boolean | ||
setMajorOpen: React.Dispatch<React.SetStateAction<boolean>> | ||
majorValue: string | ||
setMajorValue: React.Dispatch<React.SetStateAction<string>> | ||
updateNow: boolean | ||
isLoading: boolean | ||
defaultProfileValues: { | ||
major?: string | ||
} | ||
} | ||
|
||
export default function MajorSection({ | ||
majorOpen, | ||
setMajorOpen, | ||
majorValue, | ||
setMajorValue, | ||
updateNow, | ||
isLoading, | ||
defaultProfileValues | ||
}: MajorSectionProps) { | ||
return ( | ||
<> | ||
<label className="-mb-4 mt-2 text-xs">First Major</label> | ||
<div className="flex flex-col gap-1"> | ||
<Popover open={majorOpen} onOpenChange={setMajorOpen} modal={true}> | ||
<PopoverTrigger asChild> | ||
<Button | ||
aria-expanded={majorOpen} | ||
variant="outline" | ||
role="combobox" | ||
className={cn( | ||
'justify-between border-gray-200 font-normal text-neutral-600 hover:bg-white', | ||
updateNow | ||
? `${majorValue === 'none' || isLoading ? 'border-red-500 text-neutral-400' : 'border-primary'}` | ||
: majorValue === defaultProfileValues.major | ||
? 'text-neutral-400' | ||
: 'border-primary' | ||
)} | ||
> | ||
{isLoading | ||
? 'Loading...' | ||
: updateNow | ||
? majorValue === 'none' | ||
? 'Department Information Unavailable / 학과 정보 없음' | ||
: majorValue | ||
: !majorValue | ||
? defaultProfileValues.major | ||
: majorValue} | ||
<FaChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> | ||
</Button> | ||
</PopoverTrigger> | ||
<PopoverContent className="w-[555px] p-0"> | ||
<Command> | ||
<CommandInput placeholder="Search major..." /> | ||
<ScrollArea className="h-40"> | ||
<CommandEmpty>No major found.</CommandEmpty> | ||
<CommandGroup> | ||
<CommandList> | ||
{majors?.map((major) => ( | ||
<CommandItem | ||
key={major} | ||
value={major} | ||
onSelect={(currentValue) => { | ||
setMajorValue(currentValue) | ||
setMajorOpen(false) | ||
}} | ||
> | ||
<FaCheck | ||
className={cn( | ||
'mr-2 h-4 w-4', | ||
majorValue === major ? 'opacity-100' : 'opacity-0' | ||
)} | ||
/> | ||
{major} | ||
</CommandItem> | ||
))} | ||
</CommandList> | ||
</CommandGroup> | ||
</ScrollArea> | ||
</Command> | ||
</PopoverContent> | ||
</Popover> | ||
</div> | ||
</> | ||
) | ||
} |
46 changes: 46 additions & 0 deletions
46
apps/frontend/app/(main)/settings/_components/NameSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Input } from '@/components/ui/input' | ||
import { cn } from '@/lib/utils' | ||
import type { SettingsFormat } from '@/types/type' | ||
import type { FieldErrors, UseFormRegister } from 'react-hook-form' | ||
|
||
interface NameSectionProps { | ||
isLoading: boolean | ||
updateNow: boolean | ||
defaultProfileValues: { userProfile?: { realName?: string } } | ||
register: UseFormRegister<SettingsFormat> | ||
errors: FieldErrors<SettingsFormat> | ||
realName: string | ||
} | ||
|
||
export default function NameSection({ | ||
isLoading, | ||
updateNow, | ||
defaultProfileValues, | ||
register, | ||
errors, | ||
realName | ||
}: NameSectionProps) { | ||
return ( | ||
<> | ||
<label className="-mb-4 text-xs">Name</label> | ||
<Input | ||
placeholder={ | ||
isLoading | ||
? 'Loading...' | ||
: defaultProfileValues.userProfile?.realName || 'Enter your name' | ||
} | ||
disabled={!!updateNow} | ||
{...register('realName')} | ||
className={cn( | ||
realName && (errors.realName ? 'border-red-500' : 'border-primary'), | ||
'placeholder:text-neutral-400 focus-visible:ring-0 disabled:bg-neutral-200' | ||
)} | ||
/> | ||
{realName && errors.realName && ( | ||
<div className="-mt-4 inline-flex items-center text-xs text-red-500"> | ||
{errors.realName.message} | ||
</div> | ||
)} | ||
</> | ||
) | ||
} |
Oops, something went wrong.