Skip to content

Commit

Permalink
feat: resource database 🚀 (#251)
Browse files Browse the repository at this point in the history
  • Loading branch information
ramiAbdou authored May 24, 2024
1 parent 1d29e21 commit ecfbd79
Show file tree
Hide file tree
Showing 58 changed files with 2,760 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function action({ request }: ActionFunctionArgs) {
}

await db
.insertInto('resources')
.insertInto('internalResources')
.values({
id: id(),
name: data.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function loader({ request }: LoaderFunctionArgs) {

async function listResources() {
const rows = await db
.selectFrom('resources')
.selectFrom('internalResources')
.select(['id', 'name'])
.execute();

Expand Down Expand Up @@ -181,7 +181,7 @@ async function importResourceUsers(input: ImportResourceUsersInput) {
);

await db
.insertInto('resourceUsers')
.insertInto('internalResourceUsers')
.values(resourceUsers)
.onConflict((oc) => oc.doNothing())
.execute();
Expand Down
4 changes: 4 additions & 0 deletions apps/member-profile/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ STUDENT_PROFILE_URL=http://localhost:3000
# GOOGLE_MAPS_API_KEY=
# MIXPANEL_TOKEN=
# POSTMARK_API_TOKEN=
# R2_ACCESS_KEY_ID=
# R2_ACCOUNT_ID=
# R2_BUCKET_NAME=
# R2_SECRET_ACCESS_KEY=
# SENTRY_DSN=
# SLACK_CLIENT_ID=
# SLACK_TEAM_ID=
Expand Down
21 changes: 21 additions & 0 deletions apps/member-profile/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
} from '@remix-run/react';
import { withSentry } from '@sentry/remix';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import timezone from 'dayjs/plugin/timezone.js';
import updateLocale from 'dayjs/plugin/updateLocale';
import utc from 'dayjs/plugin/utc.js';

import { Toast } from '@oyster/ui';
Expand All @@ -21,7 +23,26 @@ import { commitSession, getSession, SESSION } from '@/shared/session.server';
import tailwindStylesheet from '@/tailwind.css?url';

dayjs.extend(utc);
dayjs.extend(relativeTime);
dayjs.extend(timezone);
dayjs.extend(updateLocale);

// To use relative times in Day.js, we need to extend some of the above plugins,
// and now we'll update the format of the relative time to be more concise.
// https://day.js.org/docs/en/customization/relative-time
dayjs.updateLocale('en', {
relativeTime: {
past: '%s',
s: '%ds',
m: '1m',
mm: '%dm',
h: '1h',
hh: '%dh',
d: '1d',
dd: '%dd',
M: '1mo',
},
});

export const links: LinksFunction = () => {
return [
Expand Down
20 changes: 20 additions & 0 deletions apps/member-profile/app/routes/_profile.points.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ async function getActivityHistory(
'completedActivities.id',
'completedActivities.occurredAt',
'completedActivities.points',
'completedActivities.resourceId',
'completedActivities.type',
'events.name as eventAttended',
'messagesReactedTo.channelId as messageReactedToChannelId',
Expand Down Expand Up @@ -543,6 +544,25 @@ function ActivityHistoryItemDescription({
</div>
);
})
.with('post_resource', 'upvote_resource', (type) => {
const verb = type === 'post_resource' ? 'posted' : 'upvoted';

return (
<p>
You {verb} a{' '}
<RemixLink
className="link"
to={{
pathname: Route['/resources'],
search: `id=${activity.resourceId}`,
}}
>
resource
</RemixLink>
.
</p>
);
})
.with('react_to_message', () => {
const href = `https://colorstack-family.slack.com/archives/${activity.messageReactedToChannelId}/p${activity.messageReactedToId}`;

Expand Down
167 changes: 167 additions & 0 deletions apps/member-profile/app/routes/_profile.resources.$id_.edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
redirect,
} from '@remix-run/node';
import {
Form as RemixForm,
useActionData,
useLoaderData,
useSearchParams,
} from '@remix-run/react';

import { type ResourceType, UpdateResourceInput } from '@oyster/core/resources';
import { getResource, updateResource } from '@oyster/core/resources.server';
import {
Button,
Divider,
Form,
getActionErrors,
Modal,
validateForm,
} from '@oyster/ui';

import {
ResourceDescriptionField,
ResourceLinkField,
ResourceProvider,
ResourceTagsField,
ResourceTitleField,
} from '@/shared/components/resource-form';
import { Route } from '@/shared/constants';
import {
commitSession,
ensureUserAuthenticated,
toast,
} from '@/shared/session.server';

export async function loader({ params, request }: LoaderFunctionArgs) {
await ensureUserAuthenticated(request);

const record = await getResource({
select: [
'resources.description',
'resources.link',
'resources.title',
'resources.type',
],
where: { id: params.id as string },
});

if (!record) {
throw new Response(null, { status: 404 });
}

const resource = {
...record,
type: record.type as ResourceType,
};

return json({
resource,
});
}

export async function action({ params, request }: ActionFunctionArgs) {
const session = await ensureUserAuthenticated(request);

const form = await request.formData();

const { data, errors } = validateForm(
UpdateResourceInput,
Object.fromEntries(form)
);

if (!data) {
return json({
error: '',
errors,
});
}

await updateResource(params.id as string, {
description: data.description,
link: data.link,
tags: data.tags,
title: data.title,
});

toast(session, {
message: 'Edited resource!',
type: 'success',
});

// TODO: Include query params...

return redirect(Route['/resources'], {
headers: {
'Set-Cookie': await commitSession(session),
},
});
}

const keys = UpdateResourceInput.keyof().enum;

export default function EditResourceModal() {
const { resource } = useLoaderData<typeof loader>();
const { error, errors } = getActionErrors(useActionData<typeof action>());
const [searchParams] = useSearchParams();

return (
<Modal
onCloseTo={{
pathname: Route['/resources'],
search: searchParams.toString(),
}}
>
<Modal.Header>
<Modal.Title>Edit Resource</Modal.Title>
<Modal.CloseButton />
</Modal.Header>

<RemixForm className="form" method="post">
<ResourceProvider type={resource.type}>
<ResourceTitleField
defaultValue={resource.title || undefined}
error={errors.title}
name={keys.title}
/>
<ResourceDescriptionField
defaultValue={resource.description || undefined}
error={errors.description}
name={keys.description}
/>
<ResourceTagsField
defaultValue={(resource.tags || []).map((tag) => {
return {
label: tag.name,
value: tag.id,
};
})}
error={errors.tags}
name={keys.tags}
/>

{resource.link && (
<>
<Divider />

<ResourceLinkField
defaultValue={resource.link || undefined}
error={errors.link}
name={keys.link}
/>
</>
)}
</ResourceProvider>

<Form.ErrorMessage>{error}</Form.ErrorMessage>

<Button.Group>
<Button.Submit>Save</Button.Submit>
</Button.Group>
</RemixForm>
</Modal>
);
}
Loading

0 comments on commit ecfbd79

Please sign in to comment.