Update profile form

pull/5537/head
Kamran Ahmed 7 months ago
parent e1a9dcc511
commit abf33b8f47
  1. 1
      src/components/Navigation/AccountDropdownList.tsx
  2. 71
      src/components/UpdateProfile/ProfileUsername.tsx
  3. 6
      src/components/UpdateProfile/UpdateProfileForm.tsx
  4. 116
      src/components/UpdateProfile/UpdatePublicProfileForm.tsx
  5. 2
      src/pages/account/update-profile.astro

@ -18,7 +18,6 @@ type AccountDropdownListProps = {
export function AccountDropdownList(props: AccountDropdownListProps) {
const { setIsTeamsOpen, onCreateRoadmap } = props;
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
return (
<ul>

@ -25,7 +25,12 @@ export function ProfileUsername(props: ProfileUsernameProps) {
}, [debouncedUsername]);
const checkIsUnique = async (username: string) => {
if (isLoading || username.length < 3) {
if (isLoading || !username) {
return;
}
if (username.length < 3) {
setIsUnique(false);
return;
}
@ -54,8 +59,44 @@ export function ProfileUsername(props: ProfileUsernameProps) {
return (
<div className="flex w-full flex-col">
<label htmlFor="username" className="text-sm leading-none text-slate-500">
Username
<label
htmlFor="username"
className="flex min-h-[16.5px] items-center justify-between text-sm leading-none text-slate-500"
>
<span>Profile URL</span>
{!isLoading && (
<span className="flex items-center">
{currentUsername &&
(currentUsername === username || !username || !isUnique) && (
<span className="text-xs">
Current URL{' '}
<a
href={`${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/u/${currentUsername}`}
target="_blank"
className={
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 font-mono text-xs font-medium text-purple-700 transition-colors hover:bg-purple-500 hover:text-white'
}
>
roadmap.sh/u/{currentUsername}
</a>
</span>
)}
{currentUsername !== username && username && isUnique && (
<span className="text-xs text-green-600">
URL after update{' '}
<a
href={`${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/u/${username}`}
target="_blank"
className={
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 text-xs font-medium text-purple-700 transition-colors hover:bg-purple-500 hover:text-purple-800 hover:text-white'
}
>
roadmap.sh/u/{username}
</a>
</span>
)}
</span>
)}
</label>
<div className="mt-2 flex items-center overflow-hidden rounded-lg border border-gray-300">
<span className="border-r border-gray-300 bg-gray-100 p-2">
@ -75,7 +116,8 @@ export function ProfileUsername(props: ProfileUsernameProps) {
onKeyDown={(e) => {
// only allow letters, numbers
const keyCode = e.key;
const validKey = /^[a-zA-Z0-9]*$/.test(keyCode);
const validKey =
/^[a-zA-Z0-9]*$/.test(keyCode) && username.length < 10;
if (
!validKey &&
!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(
@ -88,19 +130,20 @@ export function ProfileUsername(props: ProfileUsernameProps) {
onChange={(e) => {
setUsername((e.target as HTMLInputElement).value.toLowerCase());
}}
onBlur={(e) => checkIsUnique((e.target as HTMLInputElement).value)}
required={profileVisibility === 'public'}
/>
<span className="absolute bottom-0 right-0 top-0 flex items-center px-2">
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : isUnique === false ? (
<X className="h-4 w-4 text-red-500" />
) : isUnique === true ? (
<CheckIcon className="h-4 w-4 text-green-500" />
) : null}
</span>
{username && (
<span className="absolute bottom-0 right-0 top-0 flex items-center px-2">
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : isUnique === false ? (
<X className="h-4 w-4 text-red-500" />
) : isUnique === true ? (
<CheckIcon className="h-4 w-4 text-green-500" />
) : null}
</span>
)}
</div>
</div>
</div>

@ -2,7 +2,6 @@ import { type FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
import UploadProfilePicture from './UploadProfilePicture';
import {ArrowDown, ChevronDown} from "lucide-react";
export function UpdateProfileForm() {
const [name, setName] = useState('');
@ -113,7 +112,10 @@ export function UpdateProfileForm() {
>
Email
</label>
<a href='/account/settings' className="text-purple-700 text-xs underline hover:text-purple-800">
<a
href="/account/settings"
className="text-xs text-purple-700 underline hover:text-purple-800"
>
Visit settings page to change email
</a>
</div>

@ -13,6 +13,7 @@ import { useToast } from '../../hooks/use-toast';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
import { VisibilityDropdown } from './VisibilityDropdown.tsx';
import { ProfileUsername } from './ProfileUsername.tsx';
import UploadProfilePicture from './UploadProfilePicture.tsx';
type RoadmapType = {
id: string;
@ -37,6 +38,7 @@ export function UpdatePublicProfileForm() {
const [isEmailVisible, setIsEmailVisible] = useState(true);
const [headline, setHeadline] = useState('');
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [roadmapVisibility, setRoadmapVisibility] =
useState<AllowedRoadmapVisibility>('all');
const [customRoadmapVisibility, setCustomRoadmapVisibility] =
@ -45,7 +47,9 @@ export function UpdatePublicProfileForm() {
const [customRoadmaps, setCustomRoadmaps] = useState<string[]>([]);
const [currentUsername, setCurrentUsername] = useState('');
const [name, setName] = useState('');
const [avatar, setAvatar] = useState('');
const [github, setGithub] = useState('');
const [twitter, setTwitter] = useState('');
const [linkedin, setLinkedin] = useState('');
@ -75,6 +79,8 @@ export function UpdatePublicProfileForm() {
twitter,
linkedin,
website,
name,
email,
},
);
@ -104,26 +110,31 @@ export function UpdatePublicProfileForm() {
}
const {
name,
email,
links,
username,
profileVisibility: defaultProfileVisibility,
publicConfig,
avatar,
} = response;
setAvatar(avatar || '');
setPublicProfileUrl(username ? `/u/${username}` : '');
setUsername(username || '');
setCurrentUsername(username || '');
setName(name || '');
setEmail(email || '');
setGithub(links?.github || '');
setTwitter(links?.twitter || '');
setLinkedin(links?.linkedin || '');
setWebsite(links?.website || '');
setProfileVisibility(defaultProfileVisibility || 'public');
setHeadline(publicConfig?.headline || '');
setRoadmapVisibility(publicConfig?.roadmapVisibility || 'none');
setCustomRoadmapVisibility(publicConfig?.customRoadmapVisibility || 'none');
setRoadmapVisibility(publicConfig?.roadmapVisibility || 'all');
setCustomRoadmapVisibility(publicConfig?.customRoadmapVisibility || 'all');
setCustomRoadmaps(publicConfig?.customRoadmaps || []);
setRoadmaps(publicConfig?.roadmaps || []);
setCustomRoadmapVisibility(publicConfig?.customRoadmapVisibility || 'none');
setIsAvailableForHire(publicConfig?.isAvailableForHire || false);
setIsEmailVisible(publicConfig?.isEmailVisible ?? true);
@ -186,13 +197,13 @@ export function UpdatePublicProfileForm() {
const publicRoadmaps = profileRoadmaps.filter((r) => !r.isCustomResource);
return (
<div className="-mx-10 mt-10 border-t px-10 pt-10">
<div>
{isCreatingRoadmap && (
<CreateRoadmapModal onClose={() => setIsCreatingRoadmap(false)} />
)}
<div className="mb-1 flex flex-col justify-between gap-2 sm:flex-row">
<div className="flex flex-grow flex-col items-start gap-2 sm:flex-row">
<div className="mb-8 flex flex-col justify-between gap-2 sm:mb-1 sm:flex-row">
<div className="flex flex-grow flex-row items-center gap-2 sm:items-center">
<h3 className="mr-1 text-xl font-bold sm:text-3xl">
Personal Profile
</h3>
@ -200,7 +211,7 @@ export function UpdatePublicProfileForm() {
<a
href={publicProfileUrl}
target="_blank"
className="flex h-[30px] shrink-0 flex-row items-center gap-1 rounded-lg border border-black pl-1.5 pr-2.5 text-sm transition-colors hover:bg-black hover:text-white"
className="flex shrink-0 flex-row items-center gap-1 rounded-lg border border-black py-0.5 pl-1.5 pr-2.5 text-xs uppercase transition-colors hover:bg-black hover:text-white"
>
<ArrowUpRight className="h-3 w-3 stroke-[3]" />
Visit
@ -212,11 +223,84 @@ export function UpdatePublicProfileForm() {
setVisibility={setProfileVisibility}
/>
</div>
<p className="mt-2 hidden text-sm text-gray-400 sm:mt-0 sm:block sm:text-base">
<p className="mb-8 mt-2 hidden text-sm text-gray-400 sm:mt-0 sm:block sm:text-base">
Set up your public profile to showcase your learning progress.
</p>
<UploadProfilePicture
type="avatar"
label="Profile picture"
avatarUrl={
avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png'
}
/>
<form className="mt-6 space-y-4 pb-10" onSubmit={handleSubmit}>
<div className="flex w-full flex-col">
<label
htmlFor="name"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Name
</label>
<input
type="text"
name="name"
id="name"
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
placeholder="John Doe"
value={name}
onInput={(e) => setName((e.target as HTMLInputElement).value)}
/>
</div>
<div className="flex w-full flex-col">
<div className="flex items-center justify-between">
<label
htmlFor="email"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Email
</label>
<a
href="/account/settings"
className="text-xs text-purple-700 underline hover:text-purple-800"
>
Visit settings page to change email
</a>
</div>
<input
type="email"
name="email"
id="email"
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
disabled
placeholder="john@example.com"
value={email}
/>
<div className="flex items-center justify-end gap-2 rounded-md text-xs text-gray-400">
<div className="flex select-none items-center justify-end gap-2 rounded-md text-xs text-gray-400">
<input
type="checkbox"
name="isEmailVisible"
id="isEmailVisible"
checked={isEmailVisible}
onChange={(e) => setIsEmailVisible(e.target.checked)}
/>
<label
htmlFor="isEmailVisible"
className="flex-grow cursor-pointer py-1.5"
>
Show my email on profile
</label>
</div>
</div>
</div>
<div className="flex w-full flex-col">
<label
htmlFor="headline"
@ -459,22 +543,6 @@ export function UpdatePublicProfileForm() {
</div>
<div className="flex flex-col gap-2">
<div className="flex select-none items-center gap-2 rounded-md border px-2 hover:bg-gray-100">
<input
type="checkbox"
name="isEmailVisible"
id="isEmailVisible"
checked={isEmailVisible}
onChange={(e) => setIsEmailVisible(e.target.checked)}
/>
<label
htmlFor="isEmailVisible"
className="flex-grow cursor-pointer py-1.5"
>
Make my email public
</label>
</div>
<div className="flex select-none items-center gap-2 rounded-md border px-2 hover:bg-gray-100">
<input
type="checkbox"

@ -1,6 +1,5 @@
---
import AccountSidebar from '../../components/AccountSidebar.astro';
import { UpdateProfileForm } from '../../components/UpdateProfile/UpdateProfileForm';
import { UpdatePublicProfileForm } from '../../components/UpdateProfile/UpdatePublicProfileForm';
import AccountLayout from '../../layouts/AccountLayout.astro';
---
@ -11,7 +10,6 @@ import AccountLayout from '../../layouts/AccountLayout.astro';
initialLoadingMessage={'Loading profile'}
>
<AccountSidebar activePageId='profile' activePageTitle='Profile'>
<UpdateProfileForm client:load />
<UpdatePublicProfileForm client:load />
</AccountSidebar>
</AccountLayout>

Loading…
Cancel
Save