mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
profile in process
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useAccount } from "@/lib/providers/jazz-provider"
|
import { useAccount } from "@/lib/providers/jazz-provider"
|
||||||
import { useParams, useRouter } from "next/navigation"
|
import { useParams } from "next/navigation"
|
||||||
|
import { useState, useRef } from "react"
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { LaIcon } from "@/components/custom/la-icon"
|
|
||||||
import { Icon } from "@/components/la-editor/components/ui/icon"
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
interface ProfileStatsProps {
|
interface ProfileStatsProps {
|
||||||
@@ -12,16 +13,6 @@ interface ProfileStatsProps {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProfileLinksProps {
|
|
||||||
linklabel?: string
|
|
||||||
link?: string
|
|
||||||
topic?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProfilePagesProps {
|
|
||||||
topic?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProfileStats: React.FC<ProfileStatsProps> = ({ number, label }) => {
|
const ProfileStats: React.FC<ProfileStatsProps> = ({ number, label }) => {
|
||||||
return (
|
return (
|
||||||
<div className="text-center font-semibold text-black/60 dark:text-white">
|
<div className="text-center font-semibold text-black/60 dark:text-white">
|
||||||
@@ -31,37 +22,47 @@ const ProfileStats: React.FC<ProfileStatsProps> = ({ number, label }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileLinks: React.FC<ProfileLinksProps> = ({ linklabel, link, topic }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row items-center justify-between bg-[#121212] p-3 text-black dark:text-white">
|
|
||||||
<div className="flex flex-row items-center space-x-3">
|
|
||||||
<p className="text-base text-opacity-90">{linklabel || "Untitled"}</p>
|
|
||||||
<div className="flex cursor-pointer flex-row items-center gap-1">
|
|
||||||
<Icon name="Link" />
|
|
||||||
<p className="text-sm text-opacity-10">{link || "#"}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text0opacity-50 bg-[#1a1a1a] p-2">{topic || "Uncategorized"}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProfilePages: React.FC<ProfilePagesProps> = ({ topic }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row items-center justify-between rounded-lg bg-[#121212] p-3 text-black dark:text-white">
|
|
||||||
<div className="rounded-lg bg-[#1a1a1a] p-2 text-opacity-50">{topic || "Uncategorized"}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProfileWrapper = () => {
|
export const ProfileWrapper = () => {
|
||||||
const account = useAccount()
|
const account = useAccount()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const username = params.username as string
|
const username = params.username as string
|
||||||
|
const avatarInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const router = useRouter()
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
const [newName, setNewName] = useState(account.me?.profile?.name || "")
|
||||||
|
const [originalName, setOriginalName] = useState(account.me?.profile?.name || "")
|
||||||
|
|
||||||
const clickEdit = () => router.push("/edit-profile")
|
const editProfileClicked = () => {
|
||||||
|
setIsEditing(!isEditing)
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNewName(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveProfile = () => {
|
||||||
|
if (newName.trim() !== "") {
|
||||||
|
if (account.me && account.me.profile) {
|
||||||
|
account.me.profile.name = newName.trim()
|
||||||
|
}
|
||||||
|
setOriginalName(newName.trim())
|
||||||
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickOutside = () => {
|
||||||
|
if (isEditing) {
|
||||||
|
setNewName(originalName)
|
||||||
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editAvatar = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0]
|
||||||
|
if (file) {
|
||||||
|
console.log("File selected:", file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!account.me || !account.me.profile) {
|
if (!account.me || !account.me.profile) {
|
||||||
return (
|
return (
|
||||||
@@ -87,31 +88,36 @@ export const ProfileWrapper = () => {
|
|||||||
<div className="flex flex-1 flex-col text-black dark:text-white">
|
<div className="flex flex-1 flex-col text-black dark:text-white">
|
||||||
<div className="flex items-center justify-between p-[20px]">
|
<div className="flex items-center justify-between p-[20px]">
|
||||||
<p className="text-2xl font-semibold">Profile</p>
|
<p className="text-2xl font-semibold">Profile</p>
|
||||||
<Button
|
|
||||||
onClick={clickEdit}
|
|
||||||
className="shadow-outer ml-auto flex h-[34px] cursor-pointer flex-row space-x-2 rounded-lg bg-white px-3 text-black shadow-[1px_1px_1px_1px_rgba(0,0,0,0.3)] hover:bg-black/10 dark:bg-[#222222] dark:text-white dark:hover:opacity-60"
|
|
||||||
>
|
|
||||||
<LaIcon name="UserCog" className="text-foreground cursor-pointer" />
|
|
||||||
<span>Edit Profile</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-semibold">{username}</p>
|
<p className="text-2xl font-semibold">{username}</p>
|
||||||
<div className="flex flex-col items-center border-b border-neutral-900 bg-inherit pb-5">
|
<div className="flex flex-col items-center border-b border-neutral-900 bg-inherit pb-5">
|
||||||
<div className="flex w-full max-w-2xl align-top">
|
<div className="flex w-full max-w-2xl align-top">
|
||||||
<div className="mr-3 h-[130px] w-[130px] rounded-md bg-[#222222]" />
|
<Avatar className="mr-3 h-[130px] w-[130px] hover:opacity-80">
|
||||||
|
<AvatarImage src={account.me?.profile?.avatarUrl} alt={account.me?.profile?.name} />
|
||||||
|
<AvatarFallback onClick={() => avatarInputRef.current?.click()} className="cursor-pointer">
|
||||||
|
{account.me?.profile?.name?.charAt(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<input type="file" ref={avatarInputRef} onChange={editAvatar} accept="image/*" style={{ display: "none" }} />
|
||||||
|
|
||||||
<div className="ml-6 flex-1">
|
<div className="ml-6 flex-1">
|
||||||
<p className="mb-3 text-[25px] font-semibold">{account.me.profile.name}</p>
|
{isEditing ? (
|
||||||
<div className="mb-1 flex flex-row items-center font-light text-[24]">
|
<Input
|
||||||
@<p className="pl-1">{account.me.root?.username}</p>
|
value={newName}
|
||||||
</div>
|
onChange={changeName}
|
||||||
<a href={account.me.root?.website || "#"} className="mb-1 flex flex-row items-center text-sm font-light">
|
className="border-result mb-3 mr-3 text-[25px] font-semibold"
|
||||||
<Icon name="Link" />
|
onBlur={clickOutside}
|
||||||
<p className="pl-1">{account.me.root?.website}</p>
|
/>
|
||||||
</a>
|
) : (
|
||||||
|
<p className="mb-3 text-[25px] font-semibold">{account.me?.profile?.name}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button className="shadow-outer ml-auto flex h-[34px] cursor-pointer flex-row items-center justify-center space-x-2 rounded-lg bg-white px-3 text-center font-medium text-black shadow-[1px_1px_1px_1px_rgba(0,0,0,0.3)] hover:bg-black/10 dark:bg-[#222222] dark:text-white dark:hover:opacity-60">
|
<Button
|
||||||
Follow
|
onClick={isEditing ? saveProfile : editProfileClicked}
|
||||||
</button>
|
className="shadow-outer ml-auto flex h-[34px] cursor-pointer flex-row items-center justify-center space-x-2 rounded-lg bg-white px-3 text-center font-medium text-black shadow-[1px_1px_1px_1px_rgba(0,0,0,0.3)] hover:bg-black/10 dark:bg-[#222222] dark:text-white dark:hover:opacity-60"
|
||||||
|
>
|
||||||
|
{isEditing ? "Save" : "Edit profile"}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10 flex justify-center">
|
<div className="mt-10 flex justify-center">
|
||||||
@@ -121,17 +127,52 @@ export const ProfileWrapper = () => {
|
|||||||
<ProfileStats number={account.me.root?.topicsLearned?.length || 0} label="Learned" />
|
<ProfileStats number={account.me.root?.topicsLearned?.length || 0} label="Learned" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mx-auto py-20">
|
||||||
{/* <div className="mx-auto mt-10 w-[50%] justify-center space-y-1">
|
<p>Public profiles are coming soon</p>
|
||||||
<p className="pb-3 pl-2 text-base font-light text-white/50">Public Pages</p>
|
|
||||||
{account.me.root?.personalPages?.map((page, index) => <ProfileLinks topic={page.topic?.name} />)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto mt-10 w-[50%] justify-center space-y-1">
|
|
||||||
<p className="pb-3 pl-2 text-base font-light text-white/50">Public Links</p>
|
|
||||||
{account.me.root?.personalLinks?.map((link, index) => (
|
|
||||||
<ProfileLinks key={index} linklabel={link.title} link={link.url} topic={link.topic?.name} />
|
|
||||||
))}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// interface ProfileLinksProps {
|
||||||
|
// linklabel?: string
|
||||||
|
// link?: string
|
||||||
|
// topic?: string
|
||||||
|
// }
|
||||||
|
// interface ProfilePagesProps {
|
||||||
|
// topic?: string
|
||||||
|
// }
|
||||||
|
// const ProfileLinks: React.FC<ProfileLinksProps> = ({ linklabel, link, topic }) => {
|
||||||
|
// return (
|
||||||
|
// <div className="flex flex-row items-center justify-between bg-[#121212] p-3 text-black dark:text-white">
|
||||||
|
// <div className="flex flex-row items-center space-x-3">
|
||||||
|
// <p className="text-base text-opacity-90">{linklabel || "Untitled"}</p>
|
||||||
|
// <div className="flex cursor-pointer flex-row items-center gap-1">
|
||||||
|
// <LaIcon name="Link" />
|
||||||
|
// <p className="text-sm text-opacity-10">{link || "#"}</p>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="text0opacity-50 bg-[#1a1a1a] p-2">{topic || "Uncategorized"}</div>
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// const ProfilePages: React.FC<ProfilePagesProps> = ({ topic }) => {
|
||||||
|
// return (
|
||||||
|
// <div className="flex flex-row items-center justify-between rounded-lg bg-[#121212] p-3 text-black dark:text-white">
|
||||||
|
// <div className="rounded-lg bg-[#1a1a1a] p-2 text-opacity-50">{topic || "Uncategorized"}</div>
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
/* <a href={account.me.root?.website || "#"} className="mb-1 flex flex-row items-center text-sm font-light">
|
||||||
|
<Icon name="Link" />
|
||||||
|
<p className="pl-1">{account.me.root?.website}</p>
|
||||||
|
</a> */
|
||||||
|
/* <Button
|
||||||
|
onClick={clickEdit}
|
||||||
|
className="shadow-outer ml-auto flex h-[34px] cursor-pointer flex-row space-x-2 rounded-lg bg-white px-3 text-black shadow-[1px_1px_1px_1px_rgba(0,0,0,0.3)] hover:bg-black/10 dark:bg-[#222222] dark:text-white dark:hover:opacity-60"
|
||||||
|
>
|
||||||
|
<LaIcon name="UserCog" className="text-foreground cursor-pointer" />
|
||||||
|
<span>Edit Profile</span>
|
||||||
|
</Button> */
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { useAccount } from "@/lib/providers/jazz-provider"
|
||||||
import { LaIcon } from "../../la-icon"
|
import { LaIcon } from "../../la-icon"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
|
import { useAuth } from "@clerk/nextjs"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -9,9 +9,8 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { useAccount } from "@/lib/providers/jazz-provider"
|
import { Avatar, AvatarImage } from "@/components/ui/avatar"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useAuth } from "@clerk/nextjs"
|
|
||||||
|
|
||||||
const MenuItem = ({
|
const MenuItem = ({
|
||||||
icon,
|
icon,
|
||||||
@@ -52,7 +51,7 @@ export const ProfileSection: React.FC = () => {
|
|||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
profile: true
|
profile: true
|
||||||
})
|
})
|
||||||
const { signOut } = useAuth()
|
const { signOut, isSignedIn } = useAuth()
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
|
||||||
const closeMenu = () => setMenuOpen(false)
|
const closeMenu = () => setMenuOpen(false)
|
||||||
@@ -67,11 +66,16 @@ export const ProfileSection: React.FC = () => {
|
|||||||
aria-label="Profile"
|
aria-label="Profile"
|
||||||
className="hover:bg-accent focus-visible:ring-ring hover:text-accent-foreground flex items-center gap-1.5 truncate rounded pl-1 pr-1.5 focus-visible:outline-none focus-visible:ring-1"
|
className="hover:bg-accent focus-visible:ring-ring hover:text-accent-foreground flex items-center gap-1.5 truncate rounded pl-1 pr-1.5 focus-visible:outline-none focus-visible:ring-1"
|
||||||
>
|
>
|
||||||
<Avatar className="size-6">
|
{isSignedIn ? (
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
<Avatar className="size-6">
|
||||||
{/* <AvatarFallback>CN</AvatarFallback> */}
|
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<span className="truncate text-left text-sm font-medium -tracking-wider">{me?.profile?.name}</span>
|
) : (
|
||||||
|
<LaIcon name="User" />
|
||||||
|
)}
|
||||||
|
<span className="truncate text-left text-sm font-medium -tracking-wider">
|
||||||
|
{isSignedIn ? me?.profile?.name : "guest"}
|
||||||
|
</span>
|
||||||
<LaIcon
|
<LaIcon
|
||||||
name="ChevronDown"
|
name="ChevronDown"
|
||||||
className={`size-4 shrink-0 transition-transform duration-300 ${menuOpen ? "rotate-180" : ""}`}
|
className={`size-4 shrink-0 transition-transform duration-300 ${menuOpen ? "rotate-180" : ""}`}
|
||||||
@@ -79,35 +83,33 @@ export const ProfileSection: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-56" align="start" side="top">
|
<DropdownMenuContent className="w-56" align="start" side="top">
|
||||||
<DropdownMenuItem>
|
{isSignedIn ? (
|
||||||
<MenuItem icon="CircleUser" text="My profile" href="/profile" onClose={closeMenu} />
|
<>
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<MenuItem icon="CircleUser" text="My profile" href="/profile" onClose={closeMenu} />
|
||||||
<DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<MenuItem icon="Settings" text="Settings" href="/settings" onClose={closeMenu} />
|
<DropdownMenuSeparator />
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<MenuItem icon="LogOut" text="Log out" onClick={signOut} onClose={closeMenu} />
|
||||||
<DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<MenuItem icon="LogOut" text="Log out" onClick={signOut} onClose={closeMenu} />
|
<DropdownMenuSeparator />
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<MenuItem icon="CircleUser" text="Tauri" href="/tauri" onClose={closeMenu} />
|
||||||
<MenuItem icon="CircleUser" text="Tauri" href="/tauri" onClose={closeMenu} />
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
</>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<MenuItem icon="LogIn" text="Sign in" href="/sign-in" onClose={closeMenu} />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="flex min-w-2 grow flex-row"></div>
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
|
||||||
<Button size="icon" variant="ghost" aria-label="Settings" className="size-7 p-0">
|
|
||||||
<LaIcon name="Settings" />
|
|
||||||
</Button>
|
|
||||||
<Link href="/">
|
|
||||||
<Button size="icon" variant="ghost" aria-label="Settings" className="size-7 p-0">
|
|
||||||
<LaIcon name="House" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* <DropdownMenuItem>
|
||||||
|
<MenuItem icon="Settings" text="Settings" href="/settings" onClose={closeMenu} />
|
||||||
|
</DropdownMenuItem> */
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ import { PersonalPageLists } from "./personal-page"
|
|||||||
import { PersonalLinkLists } from "./personal-link"
|
import { PersonalLinkLists } from "./personal-link"
|
||||||
import { ListOfTopics } from "./master/topic"
|
import { ListOfTopics } from "./master/topic"
|
||||||
|
|
||||||
|
declare module "jazz-tools" {
|
||||||
|
interface Profile {
|
||||||
|
avatarUrl?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
export class UserRoot extends CoMap {
|
export class UserRoot extends CoMap {
|
||||||
name = co.string
|
name = co.string
|
||||||
username = co.string
|
username = co.string
|
||||||
@@ -33,7 +38,7 @@ export class LaAccount extends Account {
|
|||||||
profile = co.ref(Profile)
|
profile = co.ref(Profile)
|
||||||
root = co.ref(UserRoot)
|
root = co.ref(UserRoot)
|
||||||
|
|
||||||
migrate(this: LaAccount, creationProps?: { name: string }) {
|
migrate(this: LaAccount, creationProps?: { name: string; avatarUrl?: string }) {
|
||||||
// since we dont have a custom AuthProvider yet.
|
// since we dont have a custom AuthProvider yet.
|
||||||
// and still using the DemoAuth. the creationProps will only accept name.
|
// and still using the DemoAuth. the creationProps will only accept name.
|
||||||
// so just do default profile create provided by jazz-tools
|
// so just do default profile create provided by jazz-tools
|
||||||
@@ -46,7 +51,7 @@ export class LaAccount extends Account {
|
|||||||
{
|
{
|
||||||
name: creationProps.name,
|
name: creationProps.name,
|
||||||
username: creationProps.name,
|
username: creationProps.name,
|
||||||
avatar: "",
|
avatar: creationProps.avatarUrl || "",
|
||||||
website: "",
|
website: "",
|
||||||
bio: "",
|
bio: "",
|
||||||
is_public: false,
|
is_public: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user