Skip to content
This repository has been archived by the owner on Jan 16, 2025. It is now read-only.

Commit

Permalink
feat: initial work on edit/delete/view (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimitrisnl authored Feb 4, 2024
1 parent f93764e commit ad8e887
Show file tree
Hide file tree
Showing 75 changed files with 3,407 additions and 1,592 deletions.
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.10.0
2 changes: 1 addition & 1 deletion app/components/base-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function MainContent({children}: {children: React.ReactNode}) {
return (
<div className="min-h-screen flex-1 p-2">
<div className="relative h-full overflow-hidden rounded-xl border border-gray-200 bg-white shadow dark:border-white/5 dark:bg-gray-900/10 dark:shadow-none">
<main>{children}</main>
<main className="h-full">{children}</main>
</div>
</div>
);
Expand Down
29 changes: 29 additions & 0 deletions app/components/empty-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {LightBulbIcon, PlusIcon} from '@heroicons/react/24/outline';

import {Button} from './ui/button';

export function EmptyState({
title,
description,
cta,
onClick,
}: {
title: string;
description: string;
cta: string;
onClick: () => void;
}) {
return (
<div className="text-center">
<LightBulbIcon className="mx-auto h-10 w-10 text-gray-400 dark:text-gray-600" />
<h3 className="mt-2 text-base font-semibold">{title}</h3>
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
<div className="mt-6">
<Button type="button" variant="default" onClick={onClick}>
<PlusIcon className="mr-2 h-4 w-4" aria-hidden="true" />
<div>{cta}</div>
</Button>
</div>
</div>
);
}
38 changes: 34 additions & 4 deletions app/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
import {isRouteErrorResponse, useRouteError} from '@remix-run/react';

import {ErrorPage} from './error-page.tsx';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '~/components/ui/card';

export function ErrorBox({
statusCode = 500,
messages = [
"We couldn't process your request. Don't worry, we're notified and we'll resolve whatever caused this.",
],
}: {
statusCode?: number;
messages?: Array<string>;
}) {
return (
<div className="flex h-full flex-col items-center pt-8 text-center">
<Card className="w-[420px]">
<CardHeader>
<CardDescription>{statusCode}</CardDescription>
<CardTitle>Something went wrong</CardTitle>
</CardHeader>
<CardContent className="text-md text-balance text-muted-foreground">
{messages}
</CardContent>
</Card>
</div>
);
}

export function BaseErrorBoundary() {
const error = useRouteError();

if (isRouteErrorResponse(error)) {
if (error.status === 500) {
return <ErrorPage />;
return <ErrorBox />;
}

return (
// eslint-disable-next-line
<ErrorPage statusCode={error.status} messages={error.data?.errors} />
<ErrorBox statusCode={error.status} messages={error.data?.errors} />
);
}

return <ErrorPage />;
return <ErrorBox />;
}
44 changes: 0 additions & 44 deletions app/components/error-page.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const AlertDialogAction = React.forwardRef<
>(({className, ...props}, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
className={cn(buttonVariants({variant: 'destructive'}), className)}
{...props}
/>
));
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const buttonVariants = cva(
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-8 px-5 py-2',
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
Expand Down
17 changes: 11 additions & 6 deletions app/core/domain/password.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {Effect} from 'effect';

import {comparePasswords, hashPassword, parsePassword} from './password.server';
import {
comparePasswords,
hashPassword,
parsePassword,
type Password,
} from './password.server';

describe('domain/password', () => {
describe('parsing', () => {
Expand All @@ -23,7 +28,7 @@ describe('domain/password', () => {

const result = await Effect.runPromiseExit(
Effect.gen(function* (_) {
const hash = yield* _(hashPassword(password));
const hash = yield* _(hashPassword(password as Password));
expect(hash).not.toBe(password);
})
);
Expand All @@ -35,8 +40,8 @@ describe('domain/password', () => {

const result = await Effect.runPromiseExit(
Effect.gen(function* (_) {
const hash1 = yield* _(hashPassword(password));
const hash2 = yield* _(hashPassword(password));
const hash1 = yield* _(hashPassword(password as Password));
const hash2 = yield* _(hashPassword(password as Password));
expect(hash1).not.toBe(hash2);
})
);
Expand All @@ -51,7 +56,7 @@ describe('domain/password', () => {

const result = await Effect.runPromiseExit(
Effect.gen(function* (_) {
const hash = yield* _(hashPassword(password));
const hash = yield* _(hashPassword(password as Password));

const isValid = yield* _(
comparePasswords({
Expand All @@ -72,7 +77,7 @@ describe('domain/password', () => {

const result = await Effect.runPromiseExit(
Effect.gen(function* (_) {
const hash = yield* _(hashPassword(password));
const hash = yield* _(hashPassword(password as Password));

const isValid = yield* _(
comparePasswords({
Expand Down
4 changes: 4 additions & 0 deletions app/core/lib/errors.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ export class ValidationError extends Data.TaggedClass('ValidationError')<{
export class InvalidIntent {
readonly _tag = 'InvalidIntent';
}

export class AnnouncementNotFoundError {
readonly _tag = 'AnnouncementNotFoundError';
}
14 changes: 9 additions & 5 deletions app/core/use-cases/accept-invitation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ const validationSchema = Schema.struct({
export type AcceptInvitationProps = Schema.Schema.To<typeof validationSchema>;

export function acceptInvitation() {
function execute({invitationId}: AcceptInvitationProps, userId: User['id']) {
function execute({
props: {invitationId},
userId,
}: {
props: AcceptInvitationProps;
userId: User['id'];
}) {
return Effect.gen(function* (_) {
yield* _(
Effect.log(
`Use-case(accept-invitation): Accepting invitation ${invitationId}`
)
Effect.log(`(accept-invitation): Accepting invitation ${invitationId}`)
);

const records = yield* _(
Expand Down Expand Up @@ -70,7 +74,7 @@ export function acceptInvitation() {
if (!orgRecord) {
yield* _(
Effect.logError(`
Use-case(accept-invitation): Org ${org_id} not found`)
(accept-invitation): Org ${org_id} not found`)
);
return yield* _(Effect.fail(new OrgNotFoundError()));
}
Expand Down
15 changes: 8 additions & 7 deletions app/core/use-cases/change-password.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ const validationSchema = Schema.struct({
export type ChangePasswordProps = Schema.Schema.To<typeof validationSchema>;

export function changePassword() {
function execute(
{newPassword, oldPassword}: ChangePasswordProps,
userId: User['id']
) {
function execute({
props: {oldPassword, newPassword},
userId,
}: {
props: ChangePasswordProps;
userId: User['id'];
}) {
return Effect.gen(function* (_) {
yield* _(
Effect.log(
`Use-case(change-password): Changing password for user ${userId}`
)
Effect.log(`(change-password): Changing password for user ${userId}`)
);

const userRecord = yield* _(
Expand Down
19 changes: 13 additions & 6 deletions app/core/use-cases/create-announcement.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ import {
announcementContentSchema,
announcementTitleSchema,
} from '../domain/announcement.server';
import {announcementStatusSchema} from '../domain/announcement-status.server';
import type {Org} from '../domain/org.server';
import type {User} from '../domain/user.server';
import {generateUUID} from '../domain/uuid.server';

const validationSchema = Schema.struct({
title: announcementTitleSchema,
content: announcementContentSchema,
status: announcementStatusSchema,
});

export type CreateAnnouncementProps = Schema.Schema.To<typeof validationSchema>;

export function createAnnouncement() {
function execute(
{content, title}: CreateAnnouncementProps,
orgId: Org['id'],
userId: User['id']
) {
function execute({
props: {content, title, status},
orgId,
userId,
}: {
props: CreateAnnouncementProps;
orgId: Org['id'];
userId: User['id'];
}) {
return Effect.gen(function* (_) {
yield* _(
Effect.log(
`Use-case(create-announcement): Creating announcement for org:${orgId} by user:${userId}`
`(create-announcement): Creating announcement for org:${orgId} by user:${userId}`
)
);

Expand All @@ -43,6 +49,7 @@ export function createAnnouncement() {
id: announcementId,
title,
content,
status,
org_id: orgId,
created_by_user_id: userId,
})
Expand Down
18 changes: 10 additions & 8 deletions app/core/use-cases/create-invitation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ const validationSchema = Schema.struct({
export type CreateInvitationProps = Schema.Schema.To<typeof validationSchema>;

export function createInvitation() {
function execute(
{email, role}: CreateInvitationProps,
orgId: Org['id'],
userId: User['id']
) {
function execute({
props: {email, role},
userId,
orgId,
}: {
props: CreateInvitationProps;
userId: User['id'];
orgId: Org['id'];
}) {
return Effect.gen(function* (_) {
yield* _(
Effect.log(
`Use-case(create-invitation): Creating invitation for ${email}`
)
Effect.log(`(create-invitation): Creating invitation for ${email}`)
);

yield* _(invitationAuthorizationService.canCreate(userId, orgId));
Expand Down
10 changes: 8 additions & 2 deletions app/core/use-cases/create-org.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ const validationSchema = Schema.struct({
export type CreateOrgProps = Schema.Schema.To<typeof validationSchema>;

export function createOrg() {
function execute({name}: CreateOrgProps, userId: User['id']) {
function execute({
props: {name},
userId,
}: {
props: CreateOrgProps;
userId: User['id'];
}) {
return Effect.gen(function* (_) {
yield* _(Effect.log(`Use-case(create-org): Creating org ${name}`));
yield* _(Effect.log(`(create-org): Creating org ${name}`));

const userRecord = yield* _(
Effect.tryPromise({
Expand Down
7 changes: 6 additions & 1 deletion app/core/use-cases/create-org.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ describe('use-cases/create-org', () => {
const userProps = yield* _(createUser().validate(userObj));
const {user: newUser} = yield* _(createUser().execute(userProps));

return yield* _(execute(props, newUser.id));
return yield* _(
execute({
props,
userId: newUser.id,
})
);
})
);

Expand Down
2 changes: 1 addition & 1 deletion app/core/use-cases/create-user.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type CreateUserProps = Schema.Schema.To<typeof validationSchema>;
export function createUser() {
function execute({email, name, password}: CreateUserProps) {
return Effect.gen(function* (_) {
yield* _(Effect.log(`Use-case(create-user): Creating user ${email}`));
yield* _(Effect.log(`(create-user): Creating user ${email}`));

const passwordHash = yield* _(hashPassword(password));
const userId = yield* _(generateUUID());
Expand Down
4 changes: 1 addition & 3 deletions app/core/use-cases/decline-invitation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export function declineInvitation() {
function execute({invitationId}: DeclineInvitationProps) {
return Effect.gen(function* (_) {
yield* _(
Effect.log(
`Use-case(decline-invitation): Declining invitation ${invitationId}`
)
Effect.log(`(decline-invitation): Declining invitation ${invitationId}`)
);
const invitationRecord = yield* _(
Effect.tryPromise({
Expand Down
Loading

0 comments on commit ad8e887

Please sign in to comment.