Skip to content

Commit

Permalink
wip: create team with url slug
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie committed Jan 19, 2024
1 parent 5a8f9ab commit f7c3689
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 44 deletions.
6 changes: 3 additions & 3 deletions packages/app/src/app/teams/[team]/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { PropsWithChildren } from 'react'
import { SidebarNav } from '@/components/sidebar-nav'
import { Main } from '@/components/main'

export interface NextPageProps<Team = string> {
params: { team: Team }
export interface NextPageProps<TeamSlug = string> {
params: { team: TeamSlug }
searchParams?: { [key: string]: string | string[] | undefined }
}

Expand All @@ -24,7 +24,7 @@ export default function Layout({
},
{
title: 'Members',
href: `/teams/${params.team}/members`
href: `/teams/${params.team}/settings/members`
}
]

Expand Down
26 changes: 26 additions & 0 deletions packages/app/src/app/teams/[team]/settings/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Separator } from '@/components/ui/separator'
import { SettingsGeneralForm } from '@/components/teams/settings-general-form'
import { PropsWithChildren, Suspense } from 'react'
import { LoadingSpinner } from '@/components/loading-spinner'

export interface NextPageProps<Team = string> {
params: { team: Team }
searchParams?: { [key: string]: string | string[] | undefined }
}

export default function Page({ params }: PropsWithChildren<NextPageProps>) {
return (
<>
<div>
<h3 className="text-lg font-medium">Members</h3>
<p className="text-sm text-muted-foreground">
Manage the members of your team.
</p>
</div>
<Separator />
<Suspense fallback={<LoadingSpinner />}>
<SettingsGeneralForm teamId={params.team} />
</Suspense>
</>
)
}
9 changes: 2 additions & 7 deletions packages/app/src/app/teams/[team]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ export interface NextPageProps<Team = string> {
searchParams?: { [key: string]: string | string[] | undefined }
}

export default function Page({
children,
params
}: PropsWithChildren<NextPageProps>) {
export default function Page({ params }: PropsWithChildren<NextPageProps>) {
return (
<>
<div>
<h3 className="text-lg font-medium">General</h3>
<p className="text-sm text-muted-foreground">
Application wide settings.
</p>
<p className="text-sm text-muted-foreground">Team Settings</p>
</div>
<Separator />
<Suspense fallback={<LoadingSpinner />}>
Expand Down
28 changes: 23 additions & 5 deletions packages/app/src/components/team-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {

React.useEffect(() => {
if (mutation.status === 'success') {
router.push(`/teams/${mutation.data.id}/settings`)
router.push(`/teams/${mutation.data.slug}/settings`)
}
}, [router, mutation.status, mutation.data])

Expand Down Expand Up @@ -210,24 +210,42 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
<FormLabel className="sr-only">
<h1>Name</h1>
</FormLabel>
<FormControl>
<Input placeholder="Team name ..." {...field} />
<Input placeholder="Name ..." {...field} />
</FormControl>
<FormDescription>Give it a great name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="slug"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Slug</FormLabel>
<FormControl>
<Input placeholder="Slug ..." {...field} />
</FormControl>
<FormDescription>
{`This is the short name used for URLs (e.g.
'solution-architects', 'order-service')`}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="contactEmail"
render={({ field }) => (
<FormItem>
<FormLabel>
<FormLabel className="sr-only">
<h1>Contact email</h1>
</FormLabel>
<FormControl>
Expand All @@ -247,7 +265,7 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {
render={({ field }) => (
<div className="grid w-full">
<FormItem>
<FormLabel>
<FormLabel className="sr-only">
<h1>Description</h1>
</FormLabel>
<FormControl>
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/components/teams/new-form.schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { TeamsCreateSchema } from '@/server/routers/schemas/teams'

export type NewTeamFormValues = z.infer<typeof TeamsCreateSchema>
export const defaultValues: Partial<NewTeamFormValues> = {
name: ''
name: '',
slug: ''
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { z } from 'zod'

const reservedSlugs = ['app', 'admin', 'www', 'admin']

export const settingsGeneralFormSchema = z.object({
name: z.string().min(3).max(256),
slug: z
.string()
.min(3)
.max(128)
.refine(slug => !reservedSlugs.includes(slug), {
message: "Slug can't be one of reserved slugs."
}),
description: z.string().min(10).max(2048).optional()
})

Expand Down
46 changes: 29 additions & 17 deletions packages/app/src/components/teams/settings-general-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { toast } from '@/components/ui/use-toast'
import { use } from 'react'
import {
SettingGeneralFormValues,
Expand All @@ -22,53 +21,64 @@ import {
import { api } from '@/trpc/client'

export function SettingsGeneralForm({ teamId }: { teamId: string }) {
const team = use(api.teams.get.query(teamId))
const team = use(api.teams.getByName.query(teamId))

const form = useForm<SettingGeneralFormValues>({
resolver: zodResolver(settingsGeneralFormSchema),
defaultValues: {
name: team?.name,
slug: team?.slug,
description: team?.description
},
mode: 'onChange'
})

function onSubmit(data: SettingGeneralFormValues) {
toast({
title: 'You submitted the following values:',
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
)
})
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<form className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Name</FormLabel>
<FormControl>
<Input placeholder="Name ..." {...field} />
<Input disabled={true} placeholder="Name ..." {...field} />
</FormControl>
<FormDescription>This is the name of the team.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="slug"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Slug</FormLabel>
<FormControl>
<Input disabled={true} placeholder="Slug ..." {...field} />
</FormControl>
<FormDescription>
{`This is the short name used for URLs (e.g.
'solution-architects', 'order-service')`}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Description</FormLabel>
<FormControl>
<Input placeholder="Description ..." {...field} />
<Input
disabled={true}
placeholder="Description ..."
{...field}
/>
</FormControl>
<FormDescription>
This a brief description of the application instance.
Expand All @@ -77,7 +87,9 @@ export function SettingsGeneralForm({ teamId }: { teamId: string }) {
</FormItem>
)}
/>
<Button type="submit">Update settings</Button>
<Button disabled={true} type="submit">
Update settings
</Button>
</form>
</Form>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ module.exports = {
name: {
type: Sequelize.STRING
},
slug: {
type: Sequelize.STRING,
unique: true
},
description: {
type: Sequelize.STRING
},
Expand Down
11 changes: 10 additions & 1 deletion packages/app/src/db/models/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
Min,
Max,
AllowNull,
Unique,
Default
} from 'sequelize-typescript'
import { Optional } from 'sequelize'

export interface TeamAttributes {
id: string
name: string
slug: string
description?: string
createdAt?: Date
updatedAt?: Date
Expand All @@ -39,10 +41,17 @@ export class Team extends Model<TeamAttributes, TeamCreationAttributes> {

@NotEmpty
@Min(3)
@Max(256)
@Max(128)
@Column
declare name: string

@NotEmpty
@Unique
@Min(3)
@Max(128)
@Column
declare slug: string

@NotEmpty
@Min(12)
@Max(2048)
Expand Down
18 changes: 17 additions & 1 deletion packages/app/src/db/schemas/teams.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { z } from 'zod'

const reservedSlugs = ['app', 'admin', 'www', 'admin']

export const FindAndCountTeamsSchema = z.object({
limit: z.number().min(0).max(100).default(10),
offset: z.number().min(0).default(0)
})
export const FindOneTeamSchema = z.string().uuid()
export const CreateTeamSchema = z.object({
name: z.string().min(3).max(128),
slug: z
.string()
.min(3)
.max(128)
.refine(slug => !reservedSlugs.includes(slug), {
message: "Slug can't be one of reserved slugs"
}),
userId: z.string().uuid(),
description: z.string().min(3).max(255).optional(),
contactEmail: z.string().email().optional()
})

export type CreateTeamSchema = z.infer<typeof CreateTeamSchema>

export const FindOneTeamByNameSlug = z
.string()
.trim()
.toLowerCase()
.min(3)
.max(128)
export type FindOneTeamByNameSlug = z.infer<typeof FindOneTeamByNameSlug>
6 changes: 6 additions & 0 deletions packages/app/src/db/services/teams.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Team } from '@/db/models/teams'
import { User } from '@/db/models/users'
import { FindAndCountTeamsSchema, FindOneTeamSchema } from '../schemas/teams'
import type { FindOneTeamByNameSlug } from '../schemas/teams'
import type { CreateTeamSchema } from '../schemas/teams'
import { z } from 'zod'
import sequelize from '@/db/config/config'
Expand All @@ -27,6 +28,11 @@ export const findOneTeam = async (opts: z.infer<typeof FindOneTeamSchema>) =>
where: { id: opts }
})

export const findOneTeamBySlug = async (opts: FindOneTeamByNameSlug) =>
await Team.findOne({
where: { slug: opts }
})

export const findAndCountTeams = async (
opts: z.infer<typeof FindAndCountTeamsSchema>
) =>
Expand Down
26 changes: 17 additions & 9 deletions packages/app/src/server/routers/actions/teams.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { protectedProcedure } from '../../trpc'
import {
TeamsCreateSchema,
TeamsGetSchema,
TeamsListSchema
TeamsListSchema,
TeamsGetBySlugSchema
} from '../schemas/teams'
import { findAndCountTeams, findOneTeam } from '@/db/services/teams'
import {
findAndCountTeams,
findOneTeam,
findOneTeamBySlug
} from '@/db/services/teams'
import { router } from '@/server/trpc'

export const listTeams = protectedProcedure
Expand All @@ -15,13 +19,17 @@ export const getTeam = protectedProcedure
.input(TeamsGetSchema)
.query(async opts => await findOneTeam(opts.input))

export const addTeam = protectedProcedure.input(TeamsCreateSchema).mutation(
async opts => {}
// await createTeam({ ...opts.input, userId: opts.ctx.session.user.id })
)
export const getTeamBySlug = protectedProcedure
.input(TeamsGetBySlugSchema)
.query(async opts => await findOneTeamBySlug(opts.input))

// export const addTeam = protectedProcedure
// .input(TeamsCreateSchema)
// .mutation(async opts => await createTeam({}))

export const teamsRouter = router({
list: listTeams,
add: addTeam,
get: getTeam
// add: addTeam,
get: getTeam,
getByName: getTeamBySlug
})
Loading

0 comments on commit f7c3689

Please sign in to comment.