60 changed files with 944 additions and 554 deletions
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
import { TDraftEvent, TRelayGroup, TTheme, TThemeSetting } from '@common/types' |
||||
import { ElectronAPI } from '@electron-toolkit/preload' |
||||
import { Event } from 'nostr-tools' |
||||
|
||||
declare global { |
||||
interface Window { |
||||
electron: ElectronAPI |
||||
api: { |
||||
system: { |
||||
isEncryptionAvailable: () => Promise<boolean> |
||||
} |
||||
theme: { |
||||
onChange: (cb: (theme: TTheme) => void) => void |
||||
current: () => Promise<TTheme> |
||||
themeSetting: () => Promise<TThemeSetting> |
||||
set: (themeSetting: TThemeSetting) => Promise<void> |
||||
} |
||||
storage: { |
||||
getRelayGroups: () => Promise<TRelayGroup[]> |
||||
setRelayGroups: (relayGroups: TRelayGroup[]) => Promise<void> |
||||
} |
||||
nostr: { |
||||
login: (nsec: string) => Promise<{ |
||||
pubkey?: string |
||||
reason?: string |
||||
}> |
||||
logout: () => Promise<void> |
||||
getPublicKey: () => Promise<string | null> |
||||
signEvent: (draftEvent: TDraftEvent) => Promise<Event | null> |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
import { Button } from '@renderer/components/ui/button' |
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
import { LogIn } from 'lucide-react' |
||||
|
||||
export default function LoginButton({ |
||||
variant = 'titlebar' |
||||
}: { |
||||
variant?: 'titlebar' | 'sidebar' |
||||
}) { |
||||
const { checkLogin } = useNostr() |
||||
|
||||
let triggerComponent: React.ReactNode |
||||
if (variant === 'titlebar') { |
||||
triggerComponent = <LogIn /> |
||||
} else { |
||||
triggerComponent = ( |
||||
<> |
||||
<LogIn size={16} /> |
||||
<div>Login</div> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<Button variant={variant} size={variant} onClick={() => checkLogin()}> |
||||
{triggerComponent} |
||||
</Button> |
||||
) |
||||
} |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar' |
||||
import { Button } from '@renderer/components/ui/button' |
||||
import { |
||||
DropdownMenu, |
||||
DropdownMenuContent, |
||||
DropdownMenuItem, |
||||
DropdownMenuTrigger |
||||
} from '@renderer/components/ui/dropdown-menu' |
||||
import { useFetchProfile } from '@renderer/hooks' |
||||
import { toProfile } from '@renderer/lib/link' |
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey' |
||||
import { useSecondaryPage } from '@renderer/PageManager' |
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
|
||||
export default function ProfileButton({ |
||||
pubkey, |
||||
variant = 'titlebar' |
||||
}: { |
||||
pubkey: string |
||||
variant?: 'titlebar' | 'sidebar' |
||||
}) { |
||||
const { logout } = useNostr() |
||||
const { |
||||
profile: { avatar, username } |
||||
} = useFetchProfile(pubkey) |
||||
const { push } = useSecondaryPage() |
||||
const defaultAvatar = generateImageByPubkey(pubkey) |
||||
|
||||
let triggerComponent: React.ReactNode |
||||
if (variant === 'titlebar') { |
||||
triggerComponent = ( |
||||
<button> |
||||
<Avatar className="w-6 h-6 hover:opacity-90"> |
||||
<AvatarImage src={avatar} /> |
||||
<AvatarFallback> |
||||
<img src={defaultAvatar} /> |
||||
</AvatarFallback> |
||||
</Avatar> |
||||
</button> |
||||
) |
||||
} else { |
||||
triggerComponent = ( |
||||
<Button variant="sidebar" size="sidebar" className="border hover:bg-muted px-2"> |
||||
<div className="flex gap-2 items-center flex-1 w-0"> |
||||
<Avatar className="w-10 h-10"> |
||||
<AvatarImage src={avatar} /> |
||||
<AvatarFallback> |
||||
<img src={defaultAvatar} /> |
||||
</AvatarFallback> |
||||
</Avatar> |
||||
<div className="truncate font-semibold text-lg">{username}</div> |
||||
</div> |
||||
</Button> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<DropdownMenu> |
||||
<DropdownMenuTrigger className="non-draggable" asChild> |
||||
{triggerComponent} |
||||
</DropdownMenuTrigger> |
||||
<DropdownMenuContent> |
||||
<DropdownMenuItem onClick={() => push(toProfile(pubkey))}>Profile</DropdownMenuItem> |
||||
<DropdownMenuItem className="text-destructive focus:text-destructive" onClick={logout}> |
||||
Logout |
||||
</DropdownMenuItem> |
||||
</DropdownMenuContent> |
||||
</DropdownMenu> |
||||
) |
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
import LoginButton from './LoginButton' |
||||
import ProfileButton from './ProfileButton' |
||||
|
||||
export default function AccountButton({ |
||||
variant = 'titlebar' |
||||
}: { |
||||
variant?: 'titlebar' | 'sidebar' |
||||
}) { |
||||
const { pubkey } = useNostr() |
||||
|
||||
if (pubkey) { |
||||
return <ProfileButton variant={variant} pubkey={pubkey} /> |
||||
} else { |
||||
return <LoginButton variant={variant} /> |
||||
} |
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
import { Button } from '@renderer/components/ui/button' |
||||
import { |
||||
Dialog, |
||||
DialogContent, |
||||
DialogDescription, |
||||
DialogHeader, |
||||
DialogTitle |
||||
} from '@renderer/components/ui/dialog' |
||||
import { Input } from '@renderer/components/ui/input' |
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
import { Dispatch, useState } from 'react' |
||||
|
||||
export default function LoginDialog({ |
||||
open, |
||||
setOpen |
||||
}: { |
||||
open: boolean |
||||
setOpen: Dispatch<boolean> |
||||
}) { |
||||
const { login, canLogin } = useNostr() |
||||
const [nsec, setNsec] = useState('') |
||||
const [errMsg, setErrMsg] = useState<string | null>(null) |
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setNsec(e.target.value) |
||||
setErrMsg(null) |
||||
} |
||||
|
||||
const handleLogin = () => { |
||||
if (nsec === '') return |
||||
|
||||
login(nsec) |
||||
.then(() => setOpen(false)) |
||||
.catch((err) => { |
||||
setErrMsg(err.message) |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<Dialog open={open} onOpenChange={setOpen}> |
||||
<DialogContent className="w-80"> |
||||
<DialogHeader> |
||||
<DialogTitle>Sign in</DialogTitle> |
||||
<DialogDescription className="text-destructive"> |
||||
{!canLogin && 'Encryption is not available in your device.'} |
||||
</DialogDescription> |
||||
</DialogHeader> |
||||
<div className="space-y-1"> |
||||
<Input |
||||
type="password" |
||||
placeholder="nsec1.." |
||||
value={nsec} |
||||
onChange={handleInputChange} |
||||
className={errMsg ? 'border-destructive' : ''} |
||||
disabled={!canLogin} |
||||
/> |
||||
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>} |
||||
</div> |
||||
<Button onClick={handleLogin} disabled={!canLogin}> |
||||
Login |
||||
</Button> |
||||
</DialogContent> |
||||
</Dialog> |
||||
) |
||||
} |
||||
@ -1,16 +1,13 @@
@@ -1,16 +1,13 @@
|
||||
import PostDialog from '@renderer/components/PostDialog' |
||||
import { Button } from '@renderer/components/ui/button' |
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
import { PencilLine } from 'lucide-react' |
||||
|
||||
export default function PostButton() { |
||||
const { pubkey } = useNostr() |
||||
if (!pubkey) return null |
||||
|
||||
export default function PostButton({ variant = 'titlebar' }: { variant?: 'titlebar' | 'sidebar' }) { |
||||
return ( |
||||
<PostDialog> |
||||
<Button variant="titlebar" size="titlebar" title="new post"> |
||||
<Button variant={variant} size={variant} title="new post"> |
||||
<PencilLine /> |
||||
{variant === 'sidebar' && <div>Post</div>} |
||||
</Button> |
||||
</PostDialog> |
||||
) |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { toHome } from '@renderer/lib/link' |
||||
import { SecondaryPageLink } from '@renderer/PageManager' |
||||
import AccountButton from '../AccountButton' |
||||
import PostButton from '../PostButton' |
||||
import RefreshButton from '../RefreshButton' |
||||
import RelaySettingsPopover from '../RelaySettingsPopover' |
||||
|
||||
export default function PrimaryPageSidebar() { |
||||
return ( |
||||
<div className="draggable w-52 h-full shrink-0 hidden xl:flex flex-col pb-8 pt-9 pl-4 justify-between"> |
||||
<div className="space-y-2"> |
||||
<div className="text-3xl font-extrabold font-mono text-center mb-4"> |
||||
<SecondaryPageLink to={toHome()}>Jumble</SecondaryPageLink> |
||||
</div> |
||||
<PostButton variant="sidebar" /> |
||||
<RelaySettingsPopover variant="sidebar" /> |
||||
<RefreshButton variant="sidebar" /> |
||||
</div> |
||||
<AccountButton variant="sidebar" /> |
||||
</div> |
||||
) |
||||
} |
||||
@ -1,79 +1,56 @@
@@ -1,79 +1,56 @@
|
||||
import * as React from "react" |
||||
import * as React from 'react' |
||||
|
||||
import { cn } from "@renderer/lib/utils" |
||||
import { cn } from '@renderer/lib/utils' |
||||
|
||||
const Card = React.forwardRef< |
||||
HTMLDivElement, |
||||
React.HTMLAttributes<HTMLDivElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( |
||||
({ className, ...props }, ref) => ( |
||||
<div |
||||
ref={ref} |
||||
className={cn( |
||||
"rounded-lg border bg-card text-card-foreground shadow-sm", |
||||
className |
||||
)} |
||||
className={cn('rounded-lg border bg-card text-card-foreground', className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
Card.displayName = "Card" |
||||
|
||||
const CardHeader = React.forwardRef< |
||||
HTMLDivElement, |
||||
React.HTMLAttributes<HTMLDivElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
<div |
||||
ref={ref} |
||||
className={cn("flex flex-col space-y-1.5 p-6", className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
CardHeader.displayName = "CardHeader" |
||||
|
||||
const CardTitle = React.forwardRef< |
||||
HTMLParagraphElement, |
||||
React.HTMLAttributes<HTMLHeadingElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
) |
||||
) |
||||
Card.displayName = 'Card' |
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( |
||||
({ className, ...props }, ref) => ( |
||||
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} /> |
||||
) |
||||
) |
||||
CardHeader.displayName = 'CardHeader' |
||||
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>( |
||||
({ className, ...props }, ref) => ( |
||||
<h3 |
||||
ref={ref} |
||||
className={cn( |
||||
"text-2xl font-semibold leading-none tracking-tight", |
||||
className |
||||
)} |
||||
className={cn('text-2xl font-semibold leading-none tracking-tight', className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
CardTitle.displayName = "CardTitle" |
||||
) |
||||
) |
||||
CardTitle.displayName = 'CardTitle' |
||||
|
||||
const CardDescription = React.forwardRef< |
||||
HTMLParagraphElement, |
||||
React.HTMLAttributes<HTMLParagraphElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
<p |
||||
ref={ref} |
||||
className={cn("text-sm text-muted-foreground", className)} |
||||
{...props} |
||||
/> |
||||
)) |
||||
CardDescription.displayName = "CardDescription" |
||||
|
||||
const CardContent = React.forwardRef< |
||||
HTMLDivElement, |
||||
React.HTMLAttributes<HTMLDivElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> |
||||
)) |
||||
CardContent.displayName = "CardContent" |
||||
|
||||
const CardFooter = React.forwardRef< |
||||
HTMLDivElement, |
||||
React.HTMLAttributes<HTMLDivElement> |
||||
>(({ className, ...props }, ref) => ( |
||||
<div |
||||
ref={ref} |
||||
className={cn("flex items-center p-6 pt-0", className)} |
||||
{...props} |
||||
/> |
||||
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} /> |
||||
)) |
||||
CardFooter.displayName = "CardFooter" |
||||
CardDescription.displayName = 'CardDescription' |
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( |
||||
({ className, ...props }, ref) => ( |
||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> |
||||
) |
||||
) |
||||
CardContent.displayName = 'CardContent' |
||||
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( |
||||
({ className, ...props }, ref) => ( |
||||
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} /> |
||||
) |
||||
) |
||||
CardFooter.displayName = 'CardFooter' |
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } |
||||
|
||||
@ -1 +1,12 @@
@@ -1 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { TDraftEvent } from '@common/types' |
||||
import { Event } from 'nostr-tools' |
||||
|
||||
declare global { |
||||
interface Window { |
||||
nostr?: { |
||||
getPublicKey: () => Promise<string | null> |
||||
signEvent: (draftEvent: TDraftEvent) => Promise<Event | null> |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ -1,114 +0,0 @@
@@ -1,114 +0,0 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar' |
||||
import { Button } from '@renderer/components/ui/button' |
||||
import { |
||||
Dialog, |
||||
DialogContent, |
||||
DialogDescription, |
||||
DialogHeader, |
||||
DialogTitle, |
||||
DialogTrigger |
||||
} from '@renderer/components/ui/dialog' |
||||
import { |
||||
DropdownMenu, |
||||
DropdownMenuContent, |
||||
DropdownMenuItem, |
||||
DropdownMenuTrigger |
||||
} from '@renderer/components/ui/dropdown-menu' |
||||
import { Input } from '@renderer/components/ui/input' |
||||
import { useFetchProfile } from '@renderer/hooks' |
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey' |
||||
import { toProfile } from '@renderer/lib/link' |
||||
import { useSecondaryPage } from '@renderer/PageManager' |
||||
import { useNostr } from '@renderer/providers/NostrProvider' |
||||
import { LogIn } from 'lucide-react' |
||||
import { useState } from 'react' |
||||
|
||||
export default function AccountButton() { |
||||
const { pubkey } = useNostr() |
||||
|
||||
if (pubkey) { |
||||
return <ProfileButton pubkey={pubkey} /> |
||||
} else { |
||||
return <LoginButton /> |
||||
} |
||||
} |
||||
|
||||
function ProfileButton({ pubkey }: { pubkey: string }) { |
||||
const { logout } = useNostr() |
||||
const { avatar } = useFetchProfile(pubkey) |
||||
const { push } = useSecondaryPage() |
||||
const defaultAvatar = generateImageByPubkey(pubkey) |
||||
|
||||
return ( |
||||
<DropdownMenu> |
||||
<DropdownMenuTrigger className="non-draggable"> |
||||
<Avatar className="w-6 h-6 hover:opacity-90"> |
||||
<AvatarImage src={avatar} /> |
||||
<AvatarFallback> |
||||
<img src={defaultAvatar} /> |
||||
</AvatarFallback> |
||||
</Avatar> |
||||
</DropdownMenuTrigger> |
||||
<DropdownMenuContent> |
||||
<DropdownMenuItem onClick={() => push(toProfile(pubkey))}>Profile</DropdownMenuItem> |
||||
<DropdownMenuItem className="text-destructive focus:text-destructive" onClick={logout}> |
||||
Logout |
||||
</DropdownMenuItem> |
||||
</DropdownMenuContent> |
||||
</DropdownMenu> |
||||
) |
||||
} |
||||
|
||||
function LoginButton() { |
||||
const { canLogin, login } = useNostr() |
||||
const [open, setOpen] = useState(false) |
||||
const [nsec, setNsec] = useState('') |
||||
const [errMsg, setErrMsg] = useState<string | null>(null) |
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setNsec(e.target.value) |
||||
setErrMsg(null) |
||||
} |
||||
|
||||
const handleLogin = () => { |
||||
if (nsec === '') return |
||||
|
||||
login(nsec).catch((err) => { |
||||
setErrMsg(err.message) |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<Dialog open={open} onOpenChange={setOpen}> |
||||
<DialogTrigger asChild> |
||||
<Button variant="titlebar" size="titlebar"> |
||||
<LogIn /> |
||||
</Button> |
||||
</DialogTrigger> |
||||
<DialogContent className="w-80"> |
||||
<DialogHeader> |
||||
<DialogTitle>Sign in</DialogTitle> |
||||
{!canLogin && ( |
||||
<DialogDescription className="text-destructive"> |
||||
Encryption is not available in your device. |
||||
</DialogDescription> |
||||
)} |
||||
</DialogHeader> |
||||
<div className="space-y-1"> |
||||
<Input |
||||
type="password" |
||||
placeholder="nsec1.." |
||||
value={nsec} |
||||
onChange={handleInputChange} |
||||
className={errMsg ? 'border-destructive' : ''} |
||||
disabled={!canLogin} |
||||
/> |
||||
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>} |
||||
</div> |
||||
<Button onClick={handleLogin} disabled={!canLogin}> |
||||
Login |
||||
</Button> |
||||
</DialogContent> |
||||
</Dialog> |
||||
) |
||||
} |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
import { TElectronWindow } from '@common/types' |
||||
|
||||
export function isElectron(w: any): w is TElectronWindow { |
||||
return !!w.electron && !!w.api |
||||
} |
||||
|
||||
export function isMacOS() { |
||||
return isElectron(window) && window.electron.process.platform === 'darwin' |
||||
} |
||||
|
||||
export const IS_ELECTRON = isElectron(window) |
||||
@ -1,3 +0,0 @@
@@ -1,3 +0,0 @@
|
||||
export function isMacOS() { |
||||
return window.electron.process.platform === 'darwin' |
||||
} |
||||
@ -1,21 +1,18 @@
@@ -1,21 +1,18 @@
|
||||
import NoteList from '@renderer/components/NoteList' |
||||
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout' |
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider' |
||||
import NotFoundPage from '../NotFoundPage' |
||||
|
||||
export default function HashtagPage({ hashtag }: { hashtag?: string }) { |
||||
export default function HashtagPage({ id }: { id?: string }) { |
||||
const { relayUrls } = useRelaySettings() |
||||
if (!hashtag) { |
||||
return null |
||||
if (!id) { |
||||
return <NotFoundPage /> |
||||
} |
||||
const normalizedHashtag = hashtag.toLowerCase() |
||||
const hashtag = id.toLowerCase() |
||||
|
||||
return ( |
||||
<SecondaryPageLayout titlebarContent={`# ${normalizedHashtag}`}> |
||||
<NoteList |
||||
key={normalizedHashtag} |
||||
filter={{ '#t': [normalizedHashtag] }} |
||||
relayUrls={relayUrls} |
||||
/> |
||||
<SecondaryPageLayout titlebarContent={`# ${hashtag}`}> |
||||
<NoteList key={hashtag} filter={{ '#t': [hashtag] }} relayUrls={relayUrls} /> |
||||
</SecondaryPageLayout> |
||||
) |
||||
} |
||||
|
||||
@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
|
||||
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout' |
||||
|
||||
export default function BlankPage() { |
||||
export default function HomePage() { |
||||
return ( |
||||
<SecondaryPageLayout hideBackButton> |
||||
<div className="text-muted-foreground w-full h-full flex items-center justify-center"> |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout' |
||||
|
||||
export default function LoadingPage({ title }: { title?: string }) { |
||||
return ( |
||||
<SecondaryPageLayout titlebarContent={title}> |
||||
<div className="text-muted-foreground text-center"> |
||||
<div>Loading...</div> |
||||
</div> |
||||
</SecondaryPageLayout> |
||||
) |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
import { Button } from '@renderer/components/ui/button' |
||||
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout' |
||||
import { toHome } from '@renderer/lib/link' |
||||
import { useSecondaryPage } from '@renderer/PageManager' |
||||
|
||||
export default function NotFoundPage() { |
||||
const { push } = useSecondaryPage() |
||||
return ( |
||||
<SecondaryPageLayout hideBackButton> |
||||
<div className="text-muted-foreground w-full h-full flex flex-col items-center justify-center gap-2"> |
||||
<div>Lost in the void 🌌</div> |
||||
<div>(404)</div> |
||||
<Button variant="secondary" onClick={() => push(toHome())}> |
||||
Carry me home |
||||
</Button> |
||||
</div> |
||||
</SecondaryPageLayout> |
||||
) |
||||
} |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
import { match } from 'path-to-regexp' |
||||
import { isValidElement } from 'react' |
||||
import FollowingListPage from './pages/secondary/FollowingListPage' |
||||
import HashtagPage from './pages/secondary/HashtagPage' |
||||
import HomePage from './pages/secondary/HomePage' |
||||
import NotePage from './pages/secondary/NotePage' |
||||
import ProfilePage from './pages/secondary/ProfilePage' |
||||
|
||||
const ROUTES = [ |
||||
{ path: '/', element: <HomePage /> }, |
||||
{ path: '/note/:id', element: <NotePage /> }, |
||||
{ path: '/user/:id', element: <ProfilePage /> }, |
||||
{ path: '/user/:id/following', element: <FollowingListPage /> }, |
||||
{ path: '/hashtag/:id', element: <HashtagPage /> } |
||||
] |
||||
|
||||
export const routes = ROUTES.map(({ path, element }) => ({ |
||||
path, |
||||
element: isValidElement(element) ? element : null, |
||||
matcher: match(path) |
||||
})) |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite' |
||||
import react from '@vitejs/plugin-react' |
||||
import path from 'path' |
||||
|
||||
export default defineConfig({ |
||||
root: path.resolve(__dirname, 'src/renderer'), |
||||
build: { |
||||
outDir: path.resolve(__dirname, 'dist/web'), |
||||
emptyOutDir: true |
||||
}, |
||||
resolve: { |
||||
alias: { |
||||
'@renderer': path.resolve('src/renderer/src') |
||||
} |
||||
}, |
||||
plugins: [react()] |
||||
}) |
||||
Loading…
Reference in new issue