refactor: colocate page-specific components

This commit is contained in:
sk1982 2024-03-30 06:47:08 -04:00
parent f26ff35643
commit c66055def1
32 changed files with 146 additions and 149 deletions

View File

@ -1,11 +1,11 @@
'use server';
import { requireUser } from '@/actions/auth';
import { PlaylogFilterState } from '@/app/(with-header)/chuni/playlog/page';
import { db } from '@/db';
import { CHUNI_MUSIC_PROPERTIES } from '@/helpers/chuni/music';
import { chuniRating } from '@/helpers/chuni/rating';
import { sql } from 'kysely';
import { PlaylogFilterState } from '@/components/chuni/playlog-list';
const SORT_KEYS = {
Date: 'id',

View File

@ -1,6 +1,6 @@
import { PageProps } from '@/types/page';
import { headers } from 'next/headers';
import { LoginCard } from '@/components/login-card';
import { LoginCard } from './login-card';
export default async function LoginPage({ searchParams }: PageProps) {
const referer = headers().get('referer');

View File

@ -1,5 +1,5 @@
import { PageProps } from '@/types/page';
import { RegisterCard } from '@/components/register-card';
import { RegisterCard } from './register-card';
export default async function RegisterPage({ searchParams }: PageProps) {
return (<RegisterCard callback={searchParams?.['callbackUrl']?.toString()} />)

View File

@ -1,23 +1,23 @@
'use client';
import { createUserWithAccessCode, deleteUser, setUserPermissions } from '@/actions/user';
import { PermissionEditModal } from './permission-edit-modal';
import { PermissionEditModal } from '@/components/permission-edit-modal';
import { useState } from 'react';
import { USER_PERMISSION_NAMES, UserPermissions } from '@/types/permissions';
import { Button, Divider, Tooltip, Input, Accordion, AccordionItem, Link, Spacer } from '@nextui-org/react';
import { ChevronDownIcon, CreditCardIcon, PencilSquareIcon, PlusIcon } from '@heroicons/react/24/outline';
import { usePromptModal } from './prompt-modal';
import { usePromptModal } from '@/components/prompt-modal';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import { generateAccessCode } from '@/helpers/access-code';
import { useUser } from '@/helpers/use-user';
import { TbBrandAppleArcade, TbCrown, TbFileSettings, TbUserShield } from 'react-icons/tb';
import { hasPermission } from '@/helpers/permissions';
import { AimeCard } from './aime-card';
import { useErrorModal } from './error-modal';
import { AimeCard } from '@/components/aime-card';
import { useErrorModal } from '@/components/error-modal';
import { AdminUser } from '@/data/user';
import { adminAddCardToUser } from '@/actions/card';
import { TrashIcon } from '@heroicons/react/24/outline';
import { useConfirmModal } from './confirm-modal';
import { useConfirmModal } from '@/components/confirm-modal';
const PERMISSION_ICONS = new Map([
[UserPermissions.USERMOD, TbUserShield],

View File

@ -1,6 +1,6 @@
import { requireUser } from '@/actions/auth';
import { UserPermissions } from '@/types/permissions';
import { AdminUserList } from '@/components/admin-user-list';
import { AdminUserList } from './admin-user-list';
import { getUsers } from '@/data/user';
export default async function AdminUsersPage() {

View File

@ -1,9 +1,9 @@
'use client';
import { Arcade, ArcadeCab, ArcadeLink, ArcadeUser } from '@/data/arcade';
import { JoinPrivacy, Visibility } from '@/types/privacy-visibility';
import { Autocomplete, AutocompleteItem, Button, Divider, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Select, SelectItem, Tooltip } from '@nextui-org/react';
import { ChevronDownIcon, GlobeAltIcon, LinkIcon, LockClosedIcon, PencilIcon, PencilSquareIcon, PlusIcon, UserMinusIcon, UserPlusIcon } from '@heroicons/react/24/outline';
import { JoinPrivacy } from '@/types/privacy-visibility';
import { Autocomplete, AutocompleteItem, Button, Divider, Input, Select, SelectItem, Tooltip } from '@nextui-org/react';
import { LinkIcon, PencilIcon, PencilSquareIcon, PlusIcon, UserMinusIcon, UserPlusIcon } from '@heroicons/react/24/outline';
import { useRef, useState } from 'react';
import { useUser } from '@/helpers/use-user';
import { hasArcadePermission, hasPermission } from '@/helpers/permissions';
@ -13,15 +13,14 @@ import { COUNTRY_CODES } from '@/types/country';
import { ArcadeUpdate, createArcadeLink, deleteArcade, deleteArcadeLink, joinPublicArcade, removeUserFromArcade, setUserArcadePermissions, updateArcade } from '@/actions/arcade';
import { useErrorModal } from '@/components/error-modal';
import { Entries } from 'type-fest';
import { Cab } from '@/components/cab';
import { Cab } from './cab';
import Link from 'next/link';
import { useConfirmModal } from '@/components/confirm-modal';
import { XMarkIcon } from '@heroicons/react/20/solid';
import { JoinLinksModal } from '@/components/join-links-modal';
import { useRouter, useSearchParams } from 'next/navigation';
import { PermissionEditModal, PermissionEditModalUser } from '@/components/permission-edit-modal';
import { VisibilityIcon } from './visibility-icon';
import { VisibilityDropdown } from './visibility-dropdown';
import { VisibilityDropdown } from '@/components/visibility-dropdown';
export type ArcadeProps = {
arcade: Arcade,

View File

@ -1,7 +1,7 @@
import { getUser } from '@/actions/auth';
import { getArcadeCabs, getArcadeInviteLinks, getArcades, getArcadeUsers } from '@/data/arcade';
import { notFound } from 'next/navigation';
import { ArcadeDetail } from '@/components/arcade';
import { ArcadeDetail } from './arcade';
import { PrivateVisibilityError } from '@/components/private-visibility-error';
export default async function ArcadeDetailPage({ params }: { params: { arcadeId: string }}) {

View File

@ -3,7 +3,7 @@ import { getUser } from '@/actions/auth';
import { Divider, Tooltip } from '@nextui-org/react';
import { UserGroupIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { CreateArcadeButton } from '@/components/create-arcade-button';
import { CreateArcadeButton } from './create-arcade-button';
import { VisibilityIcon } from '@/components/visibility-icon';
const getLocation = (arcade: Arcade) => {

View File

@ -4,7 +4,7 @@ import { ChuniPlaylogCard } from '@/components/chuni/playlog-card';
import { getUserData, getUserRating } from '@/actions/chuni/profile';
import { requireUser } from '@/actions/auth';
import { notFound } from 'next/navigation';
import { ChuniTopRatingSidebar } from '@/components/chuni/top-rating-sidebar';
import { ChuniTopRatingSidebar } from './top-rating-sidebar';
import { Button } from '@nextui-org/react';
import Link from 'next/link';

View File

@ -1,6 +1,6 @@
'use client';
import { ChuniTopRating } from '@/components/chuni/top-rating';
import { ChuniTopRating } from './top-rating';
import { getUserRating } from '@/actions/chuni/profile';
import { useState } from 'react';
import { Button, ButtonGroup } from '@nextui-org/react';

View File

@ -5,7 +5,7 @@ import { ChuniPlaylog } from '@/actions/chuni/playlog';
import { MusicPlayer } from '@/components/music-player';
import { getJacketUrl, getMusicUrl } from '@/helpers/assets';
import { Ticker } from '@/components/ticker';
import { ChuniMusicPlaylog } from '@/components/chuni/music-playlog';
import { ChuniMusicPlaylog } from './music-playlog';
import { Button } from '@nextui-org/react';
import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid';
import { HeartIcon as OutlineHeartIcon } from '@heroicons/react/24/outline';

View File

@ -2,7 +2,7 @@ import { getMusic } from '@/actions/chuni/music';
import { notFound } from 'next/navigation';
import { getPlaylog } from '@/actions/chuni/playlog';
import { ChuniMusicDetail } from '@/components/chuni/music-detail';
import { ChuniMusicDetail } from './music-detail';
export default async function ChuniMusicDetailPage({ params }: { params: { musicId: string } }) {
const musicId = parseInt(params.musicId);

View File

@ -1,5 +1,5 @@
import { getMusic } from '@/actions/chuni/music';
import { ChuniMusicList } from '@/components/chuni/music-list';
import { ChuniMusicList } from './music-list';
export default async function ChuniMusicPage() {

View File

@ -1,6 +1,115 @@
import { ChuniPlaylogList } from '@/components/chuni/playlog-list';
'use client';
import { CHUNI_FILTER_DIFFICULTY, CHUNI_FILTER_GENRE, CHUNI_FILTER_LAMP, CHUNI_FILTER_LEVEL, CHUNI_FILTER_RATING, CHUNI_FILTER_SCORE, CHUNI_FILTER_WORLDS_END_STARS, CHUNI_FILTER_WORLDS_END_TAG, getLevelValFromStop } from '@/helpers/chuni/filter';
import { FilterField, FilterSorter } from '@/components/filter-sorter';
import { SelectItem } from '@nextui-org/react';
import { ChuniMusic } from '@/actions/chuni/music';
import { ArrayIndices } from 'type-fest';
import { ChuniPlaylog, getPlaylog } from '@/actions/chuni/playlog';
import { WindowScrollerGrid } from '@/components/window-scroller-grid';
import { ChuniPlaylogCard } from '@/components/chuni/playlog-card';
import { useBreakpoint } from '@/helpers/use-breakpoint';
export default function ChuniPlaylogPage() {
return (<ChuniPlaylogList />);
}
const FILTERERS = ([
CHUNI_FILTER_DIFFICULTY,
CHUNI_FILTER_GENRE,
{
...CHUNI_FILTER_LAMP,
props: {
children: [
<SelectItem key="aj" value="aj">All Justice</SelectItem>,
<SelectItem key="fc" value="fc">Full Combo</SelectItem>,
<SelectItem key="clear" value="clear">Clear</SelectItem>,
],
selectionMode: 'multiple'
}
},
CHUNI_FILTER_WORLDS_END_TAG,
{
...CHUNI_FILTER_SCORE,
className: 'col-span-6 md:col-span-3 lg:col-span-2 5xl:col-span-1'
},
// CHUNI_FILTER_FAVORITE,
({
type: 'dateSelect',
name: 'dateRange',
label: 'Date Range',
value: undefined,
className: 'col-span-6 md:col-span-3 lg:col-span-2 5xl:col-span-1',
filter: () => false
} as FilterField<ChuniMusic, 'dateSelect', 'dateRange'>),
{
...CHUNI_FILTER_WORLDS_END_STARS,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
},
{
...CHUNI_FILTER_LEVEL,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
},
{
...CHUNI_FILTER_RATING,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
}
] as const);
export type PlaylogFilterState = {
[K in ArrayIndices<(typeof FILTERERS)> as (typeof FILTERERS)[K]['name']]: (typeof FILTERERS[K])['value']
};
const SORTERS = [{
name: 'Date'
}, {
name: 'Rating'
}, {
name: 'Level'
}, {
name: 'Score'
}] as const;
const PER_PAGE = [25, 50, 100, 250];
const REMOTE_FILTERERS = FILTERERS.map(({ filter, ...x }) => x);
const ChuniPlaylogGrid = ({ items }: { items: ChuniPlaylog['data']; }) => {
const breakpoint = useBreakpoint();
let colSize = 1000;
let rowSize = 275;
if (breakpoint !== 'sm' && breakpoint !== undefined) {
colSize = 550;
rowSize = 200;
}
return (<WindowScrollerGrid rowSize={rowSize} colSize={colSize} items={items}>
{item => <div className="p-1 w-full h-full max-w-full">
<ChuniPlaylogCard playlog={item} showDetails
badgeClass="h-4 sm:h-5 md:-mt-3"
className="w-full h-full max-w-full" />
</div>}
</WindowScrollerGrid>);
};
export default function ChuniPlaylogList() {
return (<FilterSorter className="flex-grow"
filterers={REMOTE_FILTERERS}
defaultAscending={false}
data={({ filters: f, pageSize, currentPage, search, sort, ascending }): Promise<ChuniPlaylog> => {
const filterState = {
...f, level: [...f.level],
dateRange: f.dateRange ? { ...f.dateRange } : undefined
} as PlaylogFilterState;
filterState.level[0] = getLevelValFromStop(filterState.level[0]);
filterState.level[1] = getLevelValFromStop(filterState.level[1]);
if (filterState.dateRange?.to) {
filterState.dateRange.to = new Date(filterState.dateRange.to);
filterState.dateRange.to.setHours(23, 59, 59, 999);
}
return getPlaylog({ ...filterState, sort, limit: pageSize, offset: pageSize * (currentPage - 1), search, ascending });
}}
sorters={SORTERS} pageSizes={PER_PAGE}>
{(_, d) => <div className="w-full max-w-full flex-grow my-2">
<ChuniPlaylogGrid items={d} />
</div>}
</FilterSorter>);
};

View File

@ -1,7 +1,7 @@
import { requireUser } from '@/actions/auth';
import { getUserData } from '@/actions/chuni/profile';
import { getUserboxItems } from '@/actions/chuni/userbox';
import { ChuniUserbox } from '@/components/chuni/userbox';
import { ChuniUserbox } from './userbox';
import { Viewport } from 'next';
export const viewport: Viewport = {

View File

@ -8,13 +8,10 @@ import { ThemeSwitcherDropdown, ThemeSwitcherSwitch } from '@/components/theme-s
import { AdjustmentsHorizontalIcon } from '@heroicons/react/24/solid';
import { login, logout } from '@/actions/auth';
import { usePathname, useRouter } from 'next/navigation';
import { UserPayload } from '@/types/user';
import { MAIN_ROUTES, ROUTES, Subroute, UserOnly, filterRoute } from '@/routes';
import { useUser } from '@/helpers/use-user';
import { useBreakpoint } from '@/helpers/use-breakpoint';
import { useCookies } from 'next-client-cookies';
import { UserPermissions } from '@/types/permissions';
import { hasPermission } from '@/helpers/permissions';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
export type HeaderSidebarProps = {

View File

@ -1,6 +1,6 @@
import { LayoutProps } from '@/types/layout';
import { ClientProviders } from '@/components/client-providers';
import { HeaderSidebar } from '@/components/header-sidebar';
import { HeaderSidebar } from './header-sidebar';
export default async function HeaderLayout({children}: LayoutProps) {
return (<ClientProviders>

View File

@ -1,7 +1,7 @@
import { getCards } from '@/actions/card';
import { Divider } from '@nextui-org/react';
import { AimeCard } from '@/components/aime-card';
import { UserSettings } from '@/components/user-settings';
import { UserSettings } from './user-settings';
export default async function SettingsPage() {
const card = await getCards();

View File

@ -6,7 +6,7 @@ import { getValidHomepageRoutes } from '@/routes';
import { USER_VISIBILITY_NAMES, UserVisibility } from '@/types/user';
import { Button, Checkbox, CheckboxGroup, Divider, Select, SelectItem, SelectSection } from '@nextui-org/react';
import { useState } from 'react';
import { useErrorModal } from './error-modal';
import { useErrorModal } from '@/components/error-modal';
export const UserSettings = () => {
const user = useUser({ required: true });

View File

@ -2,7 +2,7 @@ import { getUser } from '@/actions/auth';
import { getTeamInviteLinks, getTeams, getTeamUsers } from '@/data/team';
import { notFound } from 'next/navigation';
import { PrivateVisibilityError } from '@/components/private-visibility-error';
import { TeamDetail } from '@/components/team';
import { TeamDetail } from './team';
export default async function TeamDetailPage({ params }: { params: { teamId: string }}) {
const user = await getUser();

View File

@ -2,18 +2,18 @@
import { useRef, useState } from 'react';
import { Team, TeamUser } from '@/data/team';
import { VisibilityDropdown } from './visibility-dropdown';
import { VisibilityDropdown } from '@/components/visibility-dropdown';
import { Button, Divider, Input, Select, SelectItem, Tooltip } from '@nextui-org/react';
import { JoinPrivacy } from '@/types/privacy-visibility';
import { LinkIcon, PencilIcon, UserMinusIcon, UserPlusIcon, XMarkIcon } from '@heroicons/react/24/outline';
import { useUser } from '@/helpers/use-user';
import { createTeamLink, deleteTeam, deleteTeamLink, joinPublicTeam, modifyTeam, removeUserFromTeam } from '@/actions/team';
import { useErrorModal } from './error-modal';
import { useErrorModal } from '@/components/error-modal';
import { UserPermissions } from '@/types/permissions';
import { hasPermission } from '@/helpers/permissions';
import { useConfirmModal } from './confirm-modal';
import { useConfirmModal } from '@/components/confirm-modal';
import { DB } from '@/types/db';
import { JoinLinksModal } from './join-links-modal';
import { JoinLinksModal } from '@/components/join-links-modal';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

View File

@ -1,8 +1,8 @@
'use client';
import { Button, Tooltip } from '@nextui-org/react';
import { usePromptModal } from './prompt-modal';
import { useErrorModal } from './error-modal';
import { usePromptModal } from '@/components/prompt-modal';
import { useErrorModal } from '@/components/error-modal';
import { PlusIcon } from '@heroicons/react/24/outline';
import { useUser } from '@/helpers/use-user';
import { createTeam } from '@/actions/team';

View File

@ -1,5 +1,5 @@
import { getUser } from '@/actions/auth';
import { CreateTeamButton } from '@/components/create-team-button';
import { CreateTeamButton } from './create-team-button';
import { VisibilityIcon } from '@/components/visibility-icon';
import { getTeams } from '@/data/team';
import { UserGroupIcon } from '@heroicons/react/24/outline';

View File

@ -1,108 +0,0 @@
'use client';
import { CHUNI_FILTER_DIFFICULTY, CHUNI_FILTER_GENRE, CHUNI_FILTER_LAMP, CHUNI_FILTER_LEVEL, CHUNI_FILTER_RATING, CHUNI_FILTER_SCORE, CHUNI_FILTER_WORLDS_END_STARS, CHUNI_FILTER_WORLDS_END_TAG, getLevelValFromStop } from '@/helpers/chuni/filter';
import { FilterField, FilterSorter } from '@/components/filter-sorter';
import { SelectItem } from '@nextui-org/react';
import { ChuniMusic } from '@/actions/chuni/music';
import { ArrayIndices } from 'type-fest';
import { ChuniPlaylog, getPlaylog } from '@/actions/chuni/playlog';
import { WindowScrollerGrid } from '@/components/window-scroller-grid';
import { ChuniPlaylogCard } from '@/components/chuni/playlog-card';
import { useBreakpoint } from '@/helpers/use-breakpoint';
const FILTERERS = ([
CHUNI_FILTER_DIFFICULTY,
CHUNI_FILTER_GENRE,
{ ...CHUNI_FILTER_LAMP,
props: {
children: [
<SelectItem key="aj" value="aj">All Justice</SelectItem>,
<SelectItem key="fc" value="fc">Full Combo</SelectItem>,
<SelectItem key="clear" value="clear">Clear</SelectItem>,
],
selectionMode: 'multiple'
}
},
CHUNI_FILTER_WORLDS_END_TAG,
{ ...CHUNI_FILTER_SCORE,
className: 'col-span-6 md:col-span-3 lg:col-span-2 5xl:col-span-1'
},
// CHUNI_FILTER_FAVORITE,
({
type: 'dateSelect',
name: 'dateRange',
label: 'Date Range',
value: undefined,
className: 'col-span-6 md:col-span-3 lg:col-span-2 5xl:col-span-1',
filter: () => false
} as FilterField<ChuniMusic, 'dateSelect', 'dateRange'>),
{ ...CHUNI_FILTER_WORLDS_END_STARS,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
},
{ ...CHUNI_FILTER_LEVEL,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
},
{ ...CHUNI_FILTER_RATING,
className: 'col-span-full md:col-span-6 lg:col-span-4 5xl:col-span-2'
}
] as const)
export type PlaylogFilterState = {
[K in ArrayIndices<(typeof FILTERERS)> as (typeof FILTERERS)[K]['name']]: (typeof FILTERERS[K])['value']
};
const SORTERS = [{
name: 'Date'
}, {
name: 'Rating'
}, {
name: 'Level'
}, {
name: 'Score'
}] as const;
const PER_PAGE = [25, 50, 100, 250];
const REMOTE_FILTERERS = FILTERERS.map(({ filter, ...x }) => x);
const ChuniPlaylogGrid = ({ items }: { items: ChuniPlaylog['data'] }) => {
const breakpoint = useBreakpoint();
let colSize = 1000;
let rowSize = 275;
if (breakpoint !== 'sm' && breakpoint !== undefined) {
colSize = 550;
rowSize = 200;
}
return (<WindowScrollerGrid rowSize={rowSize} colSize={colSize} items={items}>
{item => <div className="p-1 w-full h-full max-w-full">
<ChuniPlaylogCard playlog={item} showDetails
badgeClass="h-4 sm:h-5 md:-mt-3"
className="w-full h-full max-w-full" />
</div>}
</WindowScrollerGrid>);
};
export const ChuniPlaylogList = () => {
return (<FilterSorter className="flex-grow"
filterers={REMOTE_FILTERERS}
defaultAscending={false}
data={({ filters: f, pageSize, currentPage, search, sort, ascending }): Promise<ChuniPlaylog> => {
const filterState = { ...f, level: [...f.level],
dateRange: f.dateRange ? { ...f.dateRange } : undefined } as PlaylogFilterState;
filterState.level[0] = getLevelValFromStop(filterState.level[0]);
filterState.level[1] = getLevelValFromStop(filterState.level[1]);
if (filterState.dateRange?.to) {
filterState.dateRange.to = new Date(filterState.dateRange.to);
filterState.dateRange.to.setHours(23, 59, 59, 999);
}
return getPlaylog({ ...filterState, sort, limit: pageSize, offset: pageSize * (currentPage - 1), search, ascending });
}}
sorters={SORTERS} pageSizes={PER_PAGE}>
{(_, d) => <div className="w-full max-w-full flex-grow my-2">
<ChuniPlaylogGrid items={d} />
</div>}
</FilterSorter>);
};