Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new career feature implementation #682

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/assets/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

a {
color: $purpleColor;
cursor: pointer;
text-decoration: none;

&:hover {
Expand Down
4 changes: 2 additions & 2 deletions src/assets/variables.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$footerHeight: 2vh;
$headerHeight: 5vh;
$padding-lg: 5em;
$padding-md: 2.5em;
$padding-lg: 10em;
$padding-md: 5em;
$padding-sm: 1em;
4 changes: 2 additions & 2 deletions src/client/app/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { KyInstance } from 'ky-universal';
import ky from 'ky-universal';
import { PORT } from 'src/common/common-constants';
import { API_URL } from 'src/common/common-constants';

export const httpClient: KyInstance = ky.create({
headers: {
'Content-Type': 'application/json',
},
prefixUrl: `http://localhost:${PORT}/api`,
prefixUrl: API_URL,
retry: 0,
});

Expand Down
4 changes: 2 additions & 2 deletions src/client/features/blog/BlogList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import BlogListItem from './BlogListItem';
import { useBlogListQuery } from './hooks/use-blog-list-query';

const BlogList = (): JSX.Element => {
const { data: blogList = [], error, isLoading } = useBlogListQuery();
const { data: blogList = [], error, isFetching } = useBlogListQuery();
return (
<div className='ag-flexbox ag-flexColumn'>
{error && <div>Failed to load</div>}
{isLoading ? <div>Loading...</div> : blogList.map((item) => <BlogListItem key={item._id} item={item} />)}
{isFetching ? <div>Loading...</div> : blogList.map((item) => <BlogListItem key={item._id} item={item} />)}
</div>
);
};
Expand Down
7 changes: 4 additions & 3 deletions src/client/features/blog/BlogListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import type { JSX } from 'react';

import { Button } from '@mui/base';
import Link from 'next/link';
import { BLOG_PAGE_URL } from 'src/common/common-constants';
import { dayjs } from 'src/common/common-date';

import { BlogListItemProps } from './blog-types';
import styles from './BlogListItem.module.scss';

const BlogListItem = ({ item }: BlogListItemProps): JSX.Element => {
const { _id: blogId, date, title, link, linkCaption } = item;
const { _id: itemId, date, title, link, linkCaption } = item;
return (
<div className={`ag-flexbox ag-justifyContent-between ${styles.container}`} key={blogId}>
<div className={`ag-flexbox ag-justifyContent-between ${styles.container}`} key={itemId}>
<div>{dayjs(date).utc().format('MMMM DD, YYYY')}</div>
<div>{title}</div>
<div>{link}</div>
<div>{linkCaption}</div>
<Link href={`/blog/${blogId}`}>
<Link href={`${BLOG_PAGE_URL}/${itemId}`}>
<Button>Open</Button>
</Link>
</div>
Expand Down
25 changes: 12 additions & 13 deletions src/client/features/blog/blog-api.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import httpClient from 'src/client/app/http-client';
import type { IBlog, IBlogDTO } from 'src/common/types/common-blog-types';
import type { BlogDTO, BlogModel } from 'src/common/types/common-blog-types';

export const blogListRequest = async (): Promise<IBlog[]> => {
const blogListDTO = await httpClient.get('blog').json<IBlogDTO[]>();
return blogListDTO.map((item) => ({
...item,
date: new Date(item.date),
}));
const blogItemAdapter = (dto: BlogDTO): BlogModel => ({
...dto,
date: new Date(dto.date),
});

export const blogListRequest = async (): Promise<BlogModel[]> => {
const dto = await httpClient.get('blog').json<BlogDTO[]>();
return dto.map(blogItemAdapter);
};

export const blogRequest = async (id: string): Promise<IBlog> => {
const blogDTO = await httpClient.get(`blog/${id}`).json<IBlogDTO>();
return {
...blogDTO,
date: new Date(blogDTO.date),
};
export const blogItemRequest = async (id: string): Promise<BlogModel> => {
const dto = await httpClient.get(`blog/${id}`).json<BlogDTO>();
return blogItemAdapter(dto);
};
4 changes: 2 additions & 2 deletions src/client/features/blog/blog-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IBlog } from 'src/common/types/common-blog-types';
import { BlogModel } from 'src/common/types/common-blog-types';

export interface BlogListItemProps {
item: IBlog;
item: BlogModel;
}
12 changes: 8 additions & 4 deletions src/client/features/blog/hooks/use-blog-list-query.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';

import { IBlog } from '../../../../common/types/common-blog-types';
import { BlogModel } from '../../../../common/types/common-blog-types';
import { blogListRequest } from '../blog-api';
import { BLOG_LIST_QUERY_KEY } from '../blog-constants';

export const useBlogListQuery = (
{ refetchOnMount = false, enabled = false, ...restProps }: Omit<UseQueryOptions<IBlog[]>, 'queryKey' | 'queryFn'> = {
{
refetchOnMount = false,
enabled = false,
...restProps
}: Omit<UseQueryOptions<BlogModel[]>, 'queryKey' | 'queryFn'> = {
enabled: false,
refetchOnMount: false,
}
): UseQueryResult<IBlog[]> =>
useQuery<IBlog[]>({
): UseQueryResult<BlogModel[]> =>
useQuery<BlogModel[]>({
...restProps,
enabled: enabled,
queryFn: blogListRequest,
Expand Down
16 changes: 16 additions & 0 deletions src/client/features/career/CareerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { JSX } from 'react';

import CareerListItem from './CareerListItem';
import { useCareerListQuery } from './hooks/use-career-list-query';

const CareerList = (): JSX.Element => {
const { data: careerList = [], error, isFetching } = useCareerListQuery();
return (
<div className='ag-flexbox ag-flexColumn'>
{error && <div>Failed to load</div>}
{isFetching ? <div>Loading...</div> : careerList.map((item) => <CareerListItem key={item._id} item={item} />)}
</div>
);
};

export default CareerList;
5 changes: 5 additions & 0 deletions src/client/features/career/CareerListItem.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import 'src/assets/variables';

.container {
padding: $padding-sm 0;
}
31 changes: 31 additions & 0 deletions src/client/features/career/CareerListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { JSX } from 'react';

import { Button } from '@mui/base';
import Link from 'next/link';
import { CAREER_PAGE_URL } from 'src/common/common-constants';
import { dayjs } from 'src/common/common-date';

import { CareerListItemProps } from './career-types';
import styles from './CareerListItem.module.scss';

const CareerListItem = ({ item }: CareerListItemProps): JSX.Element => {
const { _id: itemId, endDate, description, post, site, startDate, title, tools } = item;
return (
<div className={`ag-flexbox ag-justifyContent-between ${styles.container}`} key={itemId}>
<div>{title}</div>
<div>
<span>{dayjs(startDate).utc().format('MMMM DD, YYYY')}</span>
{endDate ? <span> - {dayjs(endDate).utc().format('MMMM DD, YYYY')}</span> : null}
</div>
<div>{post}</div>
<div>{site}</div>
<div>{description}</div>
<div>{tools}</div>
<Link href={`${CAREER_PAGE_URL}/${itemId}`}>
<Button>Open</Button>
</Link>
</div>
);
};

export default CareerListItem;
18 changes: 18 additions & 0 deletions src/client/features/career/career-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import httpClient from 'src/client/app/http-client';
import { CareerDTO, CareerModel } from 'src/common/types/common-career-types';

const careerItemAdapter = (dto: CareerDTO): CareerModel => ({
...dto,
endDate: dto.endDate && new Date(dto.endDate),
startDate: new Date(dto.startDate),
});

export const careerListRequest = async (): Promise<CareerModel[]> => {
const dto = await httpClient.get('career').json<CareerDTO[]>();
return dto.map(careerItemAdapter);
};

export const careerItemRequest = async (id: string): Promise<CareerModel> => {
const dto = await httpClient.get(`career/${id}`).json<CareerDTO>();
return careerItemAdapter(dto);
};
3 changes: 3 additions & 0 deletions src/client/features/career/career-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CAREER_LIST_QUERY_KEY = 'CAREER_LIST_QUERY_KEY';

export const CAREER_ITEM_QUERY_KEY = 'CAREER_ITEM_QUERY_KEY';
5 changes: 5 additions & 0 deletions src/client/features/career/career-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CareerModel } from 'src/common/types/common-career-types';

export interface CareerListItemProps {
item: CareerModel;
}
24 changes: 24 additions & 0 deletions src/client/features/career/hooks/use-career-list-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { CareerModel } from 'src/common/types/common-career-types';

import { careerListRequest } from '../career-api';
import { CAREER_LIST_QUERY_KEY } from '../career-constants';

export const useCareerListQuery = (
{
refetchOnMount = false,
enabled = false,
...restProps
}: Omit<UseQueryOptions<CareerModel[]>, 'queryKey' | 'queryFn'> = {
enabled: false,
refetchOnMount: false,
}
): UseQueryResult<CareerModel[]> =>
useQuery<CareerModel[]>({
...restProps,
enabled: enabled,
queryFn: careerListRequest,
queryKey: [CAREER_LIST_QUERY_KEY],
refetchOnMount: refetchOnMount,
});
5 changes: 5 additions & 0 deletions src/client/features/summary/Summary.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import 'src/assets/variables';

.link {
padding: $padding-sm 0;
}
23 changes: 23 additions & 0 deletions src/client/features/summary/Summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { JSX } from 'react';

import Link from 'next/link';
import { BLOG_PAGE_URL, CAREER_PAGE_URL } from 'src/common/common-constants';
import ApiLink from 'src/common/components/ApiLink';

import styles from './Summary.module.scss';

const Summary = (): JSX.Element => (
<ul className='ag-flexbox ag-flexColumn'>
<li className={styles.link}>
<Link href={BLOG_PAGE_URL}>Blog page</Link>
</li>
<li className={styles.link}>
<Link href={CAREER_PAGE_URL}>Career page</Link>
</li>
<li className={styles.link}>
<ApiLink>API documentation page</ApiLink>
</li>
</ul>
);

export default Summary;
14 changes: 12 additions & 2 deletions src/common/common-constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
export const isServer = typeof window === 'undefined';

export const isDev = process.env.NODE_ENV === 'development';

export const PROTOCOL = (isServer ? global.location?.protocol : window.location?.protocol) ?? 'http';
export const HOST = (isServer ? global.location?.hostname : window.location?.hostname) ?? 'localhost';
export const PORT = process.env.PORT || 3000;
// protocol comes with ":" on client side
export const API_URL = isServer ? `${PROTOCOL}://${HOST}:${PORT}/api` : `${PROTOCOL}//${HOST}:${PORT}/api`;
// routes
export const BLOG_PAGE_ID = 'blog';
export const BLOG_PAGE_URL = `/${BLOG_PAGE_ID}`;
export const CAREER_PAGE_ID = 'career';
export const CAREER_PAGE_URL = `/${CAREER_PAGE_ID}`;

if (isDev) {
console.log('isServer', isServer);
console.log('isDev', isDev);
console.log('PROTOCOL', PROTOCOL);
console.log('HOST', HOST);
console.log('PORT', PORT);
console.log('API_URL', API_URL);
}
12 changes: 12 additions & 0 deletions src/common/components/ApiLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { PropsWithChildren, ReactElement } from 'react';

import { API_URL } from 'src/common/common-constants';
import { WithClass } from 'src/common/types/common-types';

const ApiLink = ({ children, className = '' }: PropsWithChildren<WithClass>): ReactElement => (
<a className={className} href={API_URL} target='_blank' rel='noopener noreferrer'>
{children}
</a>
);

export default ApiLink;
1 change: 1 addition & 0 deletions src/common/components/Navigation.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

.nav {
height: $headerHeight;

&Link {
padding: $padding-sm;
}
Expand Down
26 changes: 12 additions & 14 deletions src/common/components/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import type { ReactElement } from 'react';

import Link from 'next/link';
import { BLOG_PAGE_URL, CAREER_PAGE_URL } from 'src/common/common-constants';

import ApiLink from './ApiLink';
import styles from './Navigation.module.scss';

const Navigation = (): ReactElement => (
<nav className={`ag-flexbox ag-fullWidth ag-alignItems_center ag-justifyContent_center ${styles.nav}`}>
<Link href='/'>
<a className={styles.navLink}>Home</a>
</Link>
<Link href='/blog'>
<a className={styles.navLink}>Blog</a>
</Link>
<Link href='/career'>
<a className={styles.navLink}>Career</a>
</Link>
<Link href='/api'>
<a className={styles.navLink} target='_blank' rel='noopener noreferrer'>
API
</a>
</Link>
<span className={styles.navLink}>
<Link href='/'>Home</Link>
</span>
<span className={styles.navLink}>
<Link href={BLOG_PAGE_URL}>Blog</Link>
</span>
<span className={styles.navLink}>
<Link href={CAREER_PAGE_URL}>Career</Link>
</span>
<ApiLink className={styles.navLink}>API</ApiLink>
</nav>
);

Expand Down
8 changes: 4 additions & 4 deletions src/common/types/common-blog-types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { IBaseEntity } from './common-types';
import { BaseEntity } from './common-types';

export interface IBlogBase extends IBaseEntity {
export interface BlogModelBase extends BaseEntity {
link: string;
linkCaption: string;
title: string;
}

export interface IBlogDTO extends IBlogBase {
export interface BlogDTO extends BlogModelBase {
date: string;
}

export interface IBlog extends IBlogBase {
export interface BlogModel extends BlogModelBase {
date: Date;
}
19 changes: 19 additions & 0 deletions src/common/types/common-career-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BaseEntity } from './common-types';

export interface CareerModelBase extends BaseEntity {
description: string;
post: string;
site: string;
title: string;
tools: string;
}

export interface CareerDTO extends CareerModelBase {
endDate?: string;
startDate: string;
}

export interface CareerModel extends CareerModelBase {
endDate?: Date;
startDate: Date;
}
Loading