a homebrewed DnD campaign based in the Honkai: Star Rail universe
hsr honkaistarrail dnd
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

reorganize form components, add `$ui/form` path alias

+63 -32
+12 -2
app/src/lib/server/db/repos/campaign-member.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { DatabaseConn } from '..' 3 + import type { UserId } from './auth' 3 4 import type { Campaign } from './campaign' 4 5 import { user } from '../schemas/auth' 5 - import { campaignMember } from '../schemas/dnd/campaign' 6 + import { campaign, campaignMember } from '../schemas/dnd/campaign' 6 7 7 8 export type CampaignMemberTable = typeof campaignMember 8 9 export type CampaignMember = InferSelectModel<CampaignMemberTable> ··· 44 45 }) 45 46 .from(campaignMember) 46 47 .where(eq(campaignMember.campaignId, id)) 47 - .leftJoin(user, eq(campaignMember.id, user.id)) 48 + .leftJoin(user, eq(user.id, campaignMember.id)) 49 + .leftJoin(campaign, eq(campaign.id, campaignMember.campaignId)) 50 + } 51 + 52 + public async getCampaignsByMember(userId: UserId) { 53 + return await this.db 54 + .select() 55 + .from(campaignMember) 56 + .where(eq(campaignMember.memberId, userId)) 57 + .leftJoin(campaign, eq(campaign.id, campaignMember.campaignId)) 48 58 } 49 59 }
+7
app/src/lib/server/db/schemas/dnd/character.ts
··· 40 40 totalHitPoints: smallint('total_hit_points').notNull(), 41 41 tempHitPoints: smallint('temp_hit_points').default(0).notNull(), 42 42 credits: integer('credits').default(0).notNull(), 43 + pronouns: text('pronouns'), 44 + alignment: smallint('alignment'), 45 + age: text('height'), 46 + hair: text('hair'), 47 + gender: text('gender'), 48 + height: text('height'), 49 + weight: text('weight'), 43 50 createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), 44 51 updatedAt: timestamp('updated_at', { withTimezone: true }) 45 52 .defaultNow()
-1
app/src/lib/server/queries/isUsernameReserved.ts app/src/lib/server/index.ts
··· 1 1 import reservedUsernamesJson from './reservedUsernames.json' with { type: 'json' } 2 2 3 3 const reservedUsernames = new Set(...reservedUsernamesJson) 4 - 5 4 export function isUsernameReserved(username: string): boolean { 6 5 return !reservedUsernames.has(username) 7 6 }
app/src/lib/server/queries/reservedUsernames.json app/src/lib/server/reservedUsernames.json
app/src/lib/ui-components/checkbox/Checkbox.svelte app/src/lib/ui-form/checkbox/Checkbox.svelte
app/src/lib/ui-components/checkbox/CheckboxCard.svelte app/src/lib/ui-form/checkbox/CheckboxCard.svelte
app/src/lib/ui-components/checkbox/CheckboxCardGroup.svelte app/src/lib/ui-form/checkbox/CheckboxCardGroup.svelte
app/src/lib/ui-components/checkbox/CheckboxGroup.svelte app/src/lib/ui-form/checkbox/CheckboxGroup.svelte
app/src/lib/ui-components/checkbox/CheckboxGroupLabel.svelte app/src/lib/ui-form/checkbox/CheckboxGroupLabel.svelte
app/src/lib/ui-components/checkbox/exports.ts app/src/lib/ui-form/checkbox/exports.ts
app/src/lib/ui-components/checkbox/index.ts app/src/lib/ui-form/checkbox/index.ts
app/src/lib/ui-components/field/Field.svelte app/src/lib/ui-form/field/Field.svelte
app/src/lib/ui-components/field/FieldContent.svelte app/src/lib/ui-form/field/FieldContent.svelte
app/src/lib/ui-components/field/FieldDescription.svelte app/src/lib/ui-form/field/FieldDescription.svelte
app/src/lib/ui-components/field/FieldError.svelte app/src/lib/ui-form/field/FieldError.svelte
app/src/lib/ui-components/field/FieldLabel.svelte app/src/lib/ui-form/field/FieldLabel.svelte
app/src/lib/ui-components/field/exports.ts app/src/lib/ui-form/field/exports.ts
app/src/lib/ui-components/field/index.ts app/src/lib/ui-form/field/index.ts
app/src/lib/ui-components/form/FormHeader.svelte app/src/lib/ui-form/form-header/FormHeader.svelte
+1 -1
app/src/lib/ui-components/form/FormLogoIcon.svelte app/src/lib/ui-form/form-header/FormLogoIcon.svelte
··· 1 - <script> 1 + <script lang="ts"> 2 2 import { SparklesIcon } from '@lucide/svelte' 3 3 import { tv } from 'tailwind-variants' 4 4
app/src/lib/ui-components/form/index.ts app/src/lib/ui-form/form-header/index.ts
+1 -1
app/src/lib/ui-components/number-input/NumberInput.svelte app/src/lib/ui-form/number-input/NumberInput.svelte
··· 1 1 <script lang="ts"> 2 2 import type { ComponentProps } from 'svelte' 3 3 import { Button } from '$ui/components/button' 4 - import { TextInput } from '$ui/components/text-input' 4 + import { TextInput } from '$ui/form/text-input' 5 5 6 6 type NumberInputRootElement = Omit<ComponentProps<typeof TextInput>, 'after'> 7 7 type NumberInputProps = NumberInputRootElement & {
app/src/lib/ui-components/number-input/index.ts app/src/lib/ui-form/number-input/index.ts
app/src/lib/ui-components/radio/Radio.svelte app/src/lib/ui-form/radio/Radio.svelte
app/src/lib/ui-components/radio/RadioCard.svelte app/src/lib/ui-form/radio/RadioCard.svelte
app/src/lib/ui-components/radio/RadioCardGroup.svelte app/src/lib/ui-form/radio/RadioCardGroup.svelte
app/src/lib/ui-components/radio/RadioGroup.svelte app/src/lib/ui-form/radio/RadioGroup.svelte
app/src/lib/ui-components/radio/exports.ts app/src/lib/ui-form/radio/exports.ts
app/src/lib/ui-components/radio/index.ts app/src/lib/ui-form/radio/index.ts
app/src/lib/ui-components/text-input/PasswordInput.svelte app/src/lib/ui-form/text-input/PasswordInput.svelte
app/src/lib/ui-components/text-input/TextAreaInput.svelte app/src/lib/ui-form/text-input/TextAreaInput.svelte
app/src/lib/ui-components/text-input/TextInput.svelte app/src/lib/ui-form/text-input/TextInput.svelte
app/src/lib/ui-components/text-input/index.ts app/src/lib/ui-form/text-input/index.ts
+4 -5
app/src/routes/(app)/abilities/new/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { PageMeta } from '$lib/page-meta' 3 3 import { Button } from '$ui/components/button' 4 - import { Field } from '$ui/components/field' 5 - import { Heading, HeadingGroup, SubHeading } from '$ui/components/heading' 6 - import { TextAreaInput, TextInput } from '$ui/components/text-input' 4 + import { Heading, HeadingGroup } from '$ui/components/heading' 5 + import { Field } from '$ui/form/field' 6 + import { TextAreaInput, TextInput } from '$ui/form/text-input' 7 7 import { PageLayout } from '$ui/layout/page-layout' 8 8 import type { PageProps } from './$types' 9 9 ··· 18 18 <PageLayout display="flex" direction="col" class="gap-8 mx-auto" items="stretch"> 19 19 <HeadingGroup> 20 20 <Heading level={1}>Create a new ability</Heading> 21 - <SubHeading isScript>Create a new ability</SubHeading> 22 21 </HeadingGroup> 23 - <form class="flex flex-col items-start gap-10"> 22 + <form method="POST" class="flex flex-col items-start gap-10"> 24 23 <Field.Root> 25 24 <Field.Label>Ability name</Field.Label> 26 25 <TextInput
+2 -2
app/src/routes/(app)/campaigns/new/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { PageMeta } from '$lib/page-meta' 3 3 import { Button } from '$ui/components/button' 4 - import { Field } from '$ui/components/field' 5 4 import { Heading, HeadingGroup, SubHeading } from '$ui/components/heading' 6 - import { TextAreaInput, TextInput } from '$ui/components/text-input' 5 + import { Field } from '$ui/form/field' 6 + import { TextAreaInput, TextInput } from '$ui/form/text-input' 7 7 import { PageLayout } from '$ui/layout/page-layout' 8 8 import type { PageProps } from './$types' 9 9
+4 -4
app/src/routes/(app)/classes/new/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { PageMeta } from '$lib/page-meta' 3 - import { Checkbox } from '$ui/components/checkbox' 4 - import { Field } from '$ui/components/field' 5 3 import { HeadingGroup, Heading, SubHeading } from '$ui/components/heading' 6 - import { Radio } from '$ui/components/radio' 7 - import { TextAreaInput, TextInput } from '$ui/components/text-input' 4 + import { Checkbox } from '$ui/form/checkbox' 5 + import { Field } from '$ui/form/field' 6 + import { Radio } from '$ui/form/radio' 7 + import { TextAreaInput, TextInput } from '$ui/form/text-input' 8 8 import { PageLayout } from '$ui/layout/page-layout' 9 9 import type { PageProps } from './$types' 10 10
+4 -4
app/src/routes/(app)/species/new/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { PageMeta } from '$lib/page-meta' 3 3 import { Button } from '$ui/components/button' 4 - import { Checkbox } from '$ui/components/checkbox' 5 - import { Field } from '$ui/components/field' 6 4 import { Heading, HeadingGroup, SubHeading } from '$ui/components/heading' 7 - import { TextInput, TextAreaInput } from '$ui/components/text-input' 8 - import { NumberInput } from '$ui/components/number-input' 5 + import { Checkbox } from '$ui/form/checkbox' 6 + import { Field } from '$ui/form/field' 7 + import { NumberInput } from '$ui/form/number-input' 8 + import { TextInput, TextAreaInput } from '$ui/form/text-input' 9 9 import { PageLayout } from '$ui/layout/page-layout' 10 10 import type { PageProps } from './$types' 11 11
+1 -1
app/src/routes/(auth)/AuthHeader.svelte
··· 1 1 <script lang="ts"> 2 2 import type { SvelteHTMLElements } from 'svelte/elements' 3 - import { FormHeader } from '$ui/components/form' 3 + import { FormHeader } from '$ui/form/form-header' 4 4 5 5 type AuthHeaderRootElement = SvelteHTMLElements['header'] 6 6 type AuthHeaderProps = AuthHeaderRootElement & {
+3 -3
app/src/routes/(auth)/reset-password/+page.svelte
··· 3 3 import { superForm } from 'sveltekit-superforms' 4 4 import { PageMeta } from '$lib/page-meta' 5 5 import { Button, LinkButton } from '$ui/components/button' 6 - import { Field } from '$ui/components/field' 7 - import { FormHeader } from '$ui/components/form' 8 - import { TextInput } from '$ui/components/text-input' 6 + import { Field } from '$ui/form/field' 7 + import { FormHeader } from '$ui/form/form-header' 8 + import { TextInput } from '$ui/form/text-input' 9 9 import type { PageProps } from './$types' 10 10 import { AuthPageLayout } from '../components' 11 11
+2 -2
app/src/routes/(auth)/signin/+page.svelte
··· 7 7 import { PageMeta } from '$lib/page-meta' 8 8 import { Button } from '$ui/components/button' 9 9 import { Callout } from '$ui/components/callout' 10 - import { Field } from '$ui/components/field' 11 10 import { Link } from '$ui/components/link' 12 - import { TextInput, PasswordInput } from '$ui/components/text-input' 11 + import { Field } from '$ui/form/field' 12 + import { TextInput, PasswordInput } from '$ui/form/text-input' 13 13 import { AuthHeader, AuthFooter, AuthPageLayout } from '../components' 14 14 import type { PageProps } from './$types' 15 15
+2 -2
app/src/routes/(auth)/signup/+page.svelte
··· 7 7 import { PageMeta } from '$lib/page-meta' 8 8 import { Button } from '$ui/components/button' 9 9 import { Callout } from '$ui/components/callout' 10 - import { Field } from '$ui/components/field' 11 10 import { Link } from '$ui/components/link' 12 - import { TextInput, PasswordInput } from '$ui/components/text-input' 11 + import { Field } from '$ui/form/field' 12 + import { TextInput, PasswordInput } from '$ui/form/text-input' 13 13 import { AuthHeader, AuthFooter, AuthPageLayout } from '../components' 14 14 import type { PageProps } from './$types' 15 15
+17 -2
app/src/routes/users/[username]/+page.server.ts
··· 2 2 import { auth } from '$server/auth' 3 3 import { db } from '$server/db' 4 4 import { AuthRepository } from '$server/db/repos/auth' 5 + import { CampaignRepository } from '$server/db/repos/campaign' 6 + import { CampaignMemberRepository } from '$server/db/repos/campaign-member' 7 + import { CharacterRepository } from '$server/db/repos/character' 5 8 import type { PageServerLoad } from './$types' 6 9 7 10 export const load: PageServerLoad = async ({ params }) => { 11 + const characterRepo = new CharacterRepository(db) 12 + const campaignRepo = new CampaignRepository(db) 13 + const campaignMemberRepo = new CampaignMemberRepository(db) 8 14 const authRepo = new AuthRepository(db, auth) 9 - const userQuery = await authRepo.getUserByUsername(params.username) 15 + 16 + const queryUser = await authRepo.getUserByUsername(params.username) 17 + const queryCharacters = queryUser && (await characterRepo.getCharactersByUser(queryUser.id)) 18 + const queryCampaignsByOwner = queryUser && (await campaignRepo.getCampaignsByOwner(queryUser.id)) 19 + const queryCampaignsByMember = 20 + queryUser && (await campaignMemberRepo.getCampaignsByMember(queryUser.id)) 21 + 10 22 return { 11 - user: userQuery, 12 23 meta: definePageMeta({ 13 24 title: params.username, 14 25 }), 26 + user: queryUser, 27 + characters: queryCharacters, 28 + campaignsByOwner: queryCampaignsByOwner, 29 + campaignsByMember: queryCampaignsByMember, 15 30 } 16 31 }
+2 -2
app/src/routes/users/[username]/+page.svelte
··· 11 11 12 12 type UserProfileProps = PageProps 13 13 let { data, params }: UserProfileProps = $props() 14 - const { user, meta } = $derived(data) 14 + const { meta, user, characters, campaignsByOwner, campaignsByMember } = $derived(data) 15 15 </script> 16 16 17 17 <svelte:head> ··· 51 51 </span> 52 52 <span class="flex flex-col gap-4"> 53 53 <hgroup class="flex justify-between"> 54 - <Heading level={2}>Campaigns</Heading> 54 + <Heading level={2}>Campaigns participating in</Heading> 55 55 <LinkButton intent="secondary" isRound={false} href="/campaigns/new"> 56 56 <BookPlusIcon class="size-4" /> 57 57 Create Campaign
+1
app/svelte.config.js
··· 26 26 }), 27 27 alias: { 28 28 '$ui/components': 'src/lib/ui-components', 29 + '$ui/form': 'src/lib/ui-form', 29 30 '$ui/icons': 'src/lib/ui-icons', 30 31 '$ui/layout': 'src/lib/ui-layout', 31 32 '$ui/patterns': 'src/lib/ui-patterns',