mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-03 19:46:13 +01:00
Join group by URL (Closes #55)
This commit is contained in:
8
src/app/groups/add-group-by-url-button-actions.ts
Normal file
8
src/app/groups/add-group-by-url-button-actions.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getGroup } from '@/lib/api'
|
||||||
|
|
||||||
|
export async function getGroupInfoAction(groupId: string) {
|
||||||
|
'use server'
|
||||||
|
return getGroup(groupId)
|
||||||
|
}
|
||||||
91
src/app/groups/add-group-by-url-button.tsx
Normal file
91
src/app/groups/add-group-by-url-button.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { getGroupInfoAction } from '@/app/groups/add-group-by-url-button-actions'
|
||||||
|
import { saveRecentGroup } from '@/app/groups/recent-groups-helpers'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover'
|
||||||
|
import { useMediaQuery } from '@/lib/hooks'
|
||||||
|
import { Loader2, Plus } from 'lucide-react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
reload: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddGroupByUrlButton({ reload }: Props) {
|
||||||
|
const isDesktop = useMediaQuery('(min-width: 640px)')
|
||||||
|
const [url, setUrl] = useState('')
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [pending, setPending] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="secondary">
|
||||||
|
{/* <Plus className="w-4 h-4 mr-2" /> */}
|
||||||
|
<>Add by URL</>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
align={isDesktop ? 'end' : 'start'}
|
||||||
|
className="[&_p]:text-sm flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<h3 className="font-bold">Add a group by URL</h3>
|
||||||
|
<p>
|
||||||
|
If a group was shared with you, you can paste its URL here to add it
|
||||||
|
to your list.
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
className="flex gap-2"
|
||||||
|
onSubmit={async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const [, groupId] =
|
||||||
|
url.match(
|
||||||
|
new RegExp(`${window.location.origin}/groups/([^/]+)`),
|
||||||
|
) ?? []
|
||||||
|
setPending(true)
|
||||||
|
const group = groupId ? await getGroupInfoAction(groupId) : null
|
||||||
|
setPending(false)
|
||||||
|
if (!group) {
|
||||||
|
setError(true)
|
||||||
|
} else {
|
||||||
|
saveRecentGroup({ id: group.id, name: group.name })
|
||||||
|
reload()
|
||||||
|
setUrl('')
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="url"
|
||||||
|
required
|
||||||
|
placeholder="https://spliit.app/..."
|
||||||
|
className="flex-1 text-base"
|
||||||
|
value={url}
|
||||||
|
disabled={pending}
|
||||||
|
onChange={(event) => {
|
||||||
|
setUrl(event.target.value)
|
||||||
|
setError(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button size="icon" type="submit" disabled={pending}>
|
||||||
|
{pending ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
{error && (
|
||||||
|
<p className="text-destructive">
|
||||||
|
Oops, we are not able to find the group from the URL you provided…
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,29 +1,10 @@
|
|||||||
import { RecentGroupList } from '@/app/groups/recent-group-list'
|
import { RecentGroupList } from '@/app/groups/recent-group-list'
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Plus } from 'lucide-react'
|
|
||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
import Link from 'next/link'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Recently visited groups',
|
title: 'Recently visited groups',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function GroupsPage() {
|
export default async function GroupsPage() {
|
||||||
return (
|
return <RecentGroupList />
|
||||||
<>
|
|
||||||
<div className="flex justify-between items-center gap-4">
|
|
||||||
<h1 className="font-bold text-2xl">
|
|
||||||
<Link href="/groups">My groups</Link>
|
|
||||||
</h1>
|
|
||||||
<Button asChild size="icon">
|
|
||||||
<Link href="/groups/create">
|
|
||||||
<Plus className="w-4 h-4" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<RecentGroupList />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { getGroupsAction } from '@/app/groups/actions'
|
import { getGroupsAction } from '@/app/groups/actions'
|
||||||
|
import { AddGroupByUrlButton } from '@/app/groups/add-group-by-url-button'
|
||||||
import {
|
import {
|
||||||
RecentGroups,
|
RecentGroups,
|
||||||
getArchivedGroups,
|
getArchivedGroups,
|
||||||
@@ -10,7 +11,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { getGroups } from '@/lib/api'
|
import { getGroups } from '@/lib/api'
|
||||||
import { Loader2 } from 'lucide-react'
|
import { Loader2 } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { SetStateAction, useEffect, useState } from 'react'
|
import { PropsWithChildren, SetStateAction, useEffect, useState } from 'react'
|
||||||
import { RecentGroupListCard } from './recent-group-list-card'
|
import { RecentGroupListCard } from './recent-group-list-card'
|
||||||
|
|
||||||
export type RecentGroupsState =
|
export type RecentGroupsState =
|
||||||
@@ -54,7 +55,7 @@ function sortGroups(
|
|||||||
export function RecentGroupList() {
|
export function RecentGroupList() {
|
||||||
const [state, setState] = useState<RecentGroupsState>({ status: 'pending' })
|
const [state, setState] = useState<RecentGroupsState>({ status: 'pending' })
|
||||||
|
|
||||||
useEffect(() => {
|
function loadGroups() {
|
||||||
const groupsInStorage = getRecentGroups()
|
const groupsInStorage = getRecentGroups()
|
||||||
const starredGroups = getStarredGroups()
|
const starredGroups = getStarredGroups()
|
||||||
const archivedGroups = getArchivedGroups()
|
const archivedGroups = getArchivedGroups()
|
||||||
@@ -73,35 +74,43 @@ export function RecentGroupList() {
|
|||||||
archivedGroups,
|
archivedGroups,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadGroups()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (state.status === 'pending') {
|
if (state.status === 'pending') {
|
||||||
return (
|
return (
|
||||||
<p>
|
<GroupsPage reload={loadGroups}>
|
||||||
<Loader2 className="w-4 m-4 mr-2 inline animate-spin" /> Loading recent
|
<p>
|
||||||
groups…
|
<Loader2 className="w-4 m-4 mr-2 inline animate-spin" /> Loading
|
||||||
</p>
|
recent groups…
|
||||||
|
</p>
|
||||||
|
</GroupsPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.groups.length === 0) {
|
if (state.groups.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-sm space-y-2">
|
<GroupsPage reload={loadGroups}>
|
||||||
<p>You have not visited any group recently.</p>
|
<div className="text-sm space-y-2">
|
||||||
<p>
|
<p>You have not visited any group recently.</p>
|
||||||
<Button variant="link" asChild className="-m-4">
|
<p>
|
||||||
<Link href={`/groups/create`}>Create one</Link>
|
<Button variant="link" asChild className="-m-4">
|
||||||
</Button>{' '}
|
<Link href={`/groups/create`}>Create one</Link>
|
||||||
or ask a friend to send you the link to an existing one.
|
</Button>{' '}
|
||||||
</p>
|
or ask a friend to send you the link to an existing one.
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
</GroupsPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { starredGroupInfo, groupInfo, archivedGroupInfo } = sortGroups(state)
|
const { starredGroupInfo, groupInfo, archivedGroupInfo } = sortGroups(state)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<GroupsPage reload={loadGroups}>
|
||||||
{starredGroupInfo.length > 0 && (
|
{starredGroupInfo.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 className="mb-2">Starred groups</h2>
|
<h2 className="mb-2">Starred groups</h2>
|
||||||
@@ -132,7 +141,7 @@ export function RecentGroupList() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</GroupsPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,3 +167,28 @@ function GroupList({
|
|||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GroupsPage({
|
||||||
|
children,
|
||||||
|
reload,
|
||||||
|
}: PropsWithChildren<{ reload: () => void }>) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
|
<h1 className="font-bold text-2xl flex-1">
|
||||||
|
<Link href="/groups">My groups</Link>
|
||||||
|
</h1>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<AddGroupByUrlButton reload={reload} />
|
||||||
|
<Button asChild>
|
||||||
|
<Link href="/groups/create">
|
||||||
|
{/* <Plus className="w-4 h-4 mr-2" /> */}
|
||||||
|
<>Create</>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>{children}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
29
src/app/groups/recent-groups-page.tsx
Normal file
29
src/app/groups/recent-groups-page.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
'use client'
|
||||||
|
import { AddGroupByUrlButton } from '@/app/groups/add-group-by-url-button'
|
||||||
|
import { RecentGroupList } from '@/app/groups/recent-group-list'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export function RecentGroupsPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
|
<h1 className="font-bold text-2xl flex-1">
|
||||||
|
<Link href="/groups">My groups</Link>
|
||||||
|
</h1>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<AddGroupByUrlButton reload={() => {}} />
|
||||||
|
<Button asChild>
|
||||||
|
<Link href="/groups/create">
|
||||||
|
{/* <Plus className="w-4 h-4 mr-2" /> */}
|
||||||
|
<>Create</>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<RecentGroupList />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user