Skip to content

Commit

Permalink
Merge pull request #280 from dump-hr/speakers-section
Browse files Browse the repository at this point in the history
Speakers section
  • Loading branch information
bdeak4 authored Apr 10, 2024
2 parents e3b12f6 + d22037f commit 13f9c3e
Show file tree
Hide file tree
Showing 24 changed files with 849 additions and 8 deletions.
2 changes: 2 additions & 0 deletions apps/admin/src/components/FileUpload/FileUpload.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
margin-top: 20px;
max-width: 400px;
max-height: 150px;
background-size: contain;
background-position: center;
object-fit: contain;
}

Expand Down
18 changes: 18 additions & 0 deletions apps/admin/src/forms/SpeakerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ export const SpeakerForm: React.FC<SpeakerFormProps> = ({ id, onSuccess }) => {
title: 'CompanyId (0 for null)',
defaultValue: speaker?.companyId,
},
{
id: 'linkedin',
type: QuestionType.Field,
title: 'LinkedIn',
defaultValue: speaker?.linkedin,
},
{
id: 'instagram',
type: QuestionType.Field,
title: 'Instagram',
defaultValue: speaker?.instagram,
},
{
id: 'description',
type: QuestionType.TextArea,
title: 'Opis (sa instagrama)',
defaultValue: speaker?.description,
},
];

const form = useForm<SpeakerModifyDto>({
Expand Down
2 changes: 1 addition & 1 deletion apps/api/db/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@
"breakpoints": true
}
]
}
}
3 changes: 3 additions & 0 deletions apps/api/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ export const speaker = pgTable('speaker', {
title: text('title').notNull(),
companyId: integer('company_id').references(() => company.id),
photo: text('photo'),
instagram: text('instagram'),
linkedin: text('linkedin'),
description: text('description'),
});

export const speakerRelations = relations(speaker, ({ one, many }) => ({
Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/speaker/speaker.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { SpeakerDto, SpeakerModifyDto } from '@ddays-app/types';
import {
SpeakerDto,
SpeakerModifyDto,
SpeakerWithCompanyDto,
} from '@ddays-app/types';
import {
Body,
Controller,
Expand Down Expand Up @@ -36,6 +40,11 @@ export class SpeakerController {
return await this.speakerService.getAll();
}

@Get('with-company')
async getAllSpeakersWithCompany(): Promise<SpeakerWithCompanyDto[]> {
return await this.speakerService.getAllSpeakersWithCompany();
}

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number): Promise<SpeakerDto> {
return await this.speakerService.getOne(id);
Expand Down
41 changes: 39 additions & 2 deletions apps/api/src/speaker/speaker.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { SpeakerDto, SpeakerModifyDto } from '@ddays-app/types';
import {
SpeakerDto,
SpeakerModifyDto,
SpeakerWithCompanyDto,
} from '@ddays-app/types';
import { Injectable } from '@nestjs/common';
import { db } from 'db';
import { speaker } from 'db/schema';
import { company, speaker } from 'db/schema';
import { eq } from 'drizzle-orm';
import { BlobService } from 'src/blob/blob.service';

Expand All @@ -28,6 +32,9 @@ export class SpeakerService {
title: speaker.title,
companyId: speaker.companyId,
photo: speaker.photo,
instagram: speaker.instagram,
linkedin: speaker.linkedin,
description: speaker.description,
})
.from(speaker)
.orderBy(speaker.firstName);
Expand All @@ -44,13 +51,43 @@ export class SpeakerService {
title: speaker.title,
companyId: speaker.companyId,
photo: speaker.photo,
instagram: speaker.instagram,
linkedin: speaker.linkedin,
description: speaker.description,
})
.from(speaker)
.where(eq(speaker.id, id));

return foundSpeaker;
}

async getAllSpeakersWithCompany() {
const result = await db
.select()
.from(speaker)
.leftJoin(company, eq(speaker.companyId, company.id))
.orderBy(speaker.firstName);

const speakersWithCompany: SpeakerWithCompanyDto[] = result.map(
(speakerCompany) => {
return {
id: speakerCompany.speaker.id,
firstName: speakerCompany.speaker.firstName,
lastName: speakerCompany.speaker.lastName,
title: speakerCompany.speaker.title,
companyId: speakerCompany.speaker.companyId,
photo: speakerCompany.speaker.photo,
instagram: speakerCompany.speaker.instagram,
linkedin: speakerCompany.speaker.linkedin,
description: speakerCompany.speaker.description,
company: speakerCompany.company,
};
},
);

return speakersWithCompany;
}

async remove(id: number) {
const [deletedSpeaker] = await db
.delete(speaker)
Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"@studio-freight/react-lenis": "^0.0.46",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"date-fns": "^3.0.0",
"dayjs": "^1.11.10",
"framer-motion": "^11.0.24",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/api/speaker/useSpeakerGetAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SpeakerDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const speakerGetAll = () => {
return api.get<never, SpeakerDto[]>('speaker');
};

export const useSpeakerGetAll = (options?: QueryOptions<SpeakerDto[]>) => {
return useQuery(['speaker'], speakerGetAll, options);
};
14 changes: 14 additions & 0 deletions apps/web/src/api/speaker/useSpeakerWithCompanyGetAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SpeakerWithCompanyDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const speakerWithCompanyGetAll = () => {
return api.get<never, SpeakerWithCompanyDto[]>('speaker/with-company');
};

export const useSpeakerWithCompanyGetAll = (
options?: QueryOptions<SpeakerWithCompanyDto[]>,
) => {
return useQuery(['speaker/with-company'], speakerWithCompanyGetAll, options);
};
9 changes: 9 additions & 0 deletions apps/web/src/assets/FilmFrame.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/web/src/assets/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions apps/web/src/components/FilmFrame/FilmFrame.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.frame {
height: 100%;
width: 100%;

&Wrapper {
position: relative;
background-color: #171615;
}

&Image {
position: absolute;
top: 3%;
left: 4%;
width: 91%;
height: 92%;
}
}
19 changes: 19 additions & 0 deletions apps/web/src/components/FilmFrame/FilmFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import FilmFrameSvg from '../../assets/FilmFrame.svg';
import c from './FilmFrame.module.scss';

type FilmFrameProps = {
imageSrc: string | undefined;
width: number;
height: number;
};

const FilmFrame: React.FC<FilmFrameProps> = ({ imageSrc, width, height }) => {
return (
<div style={{ height: height, width: width }} className={c.frameWrapper}>
<img className={c.frame} src={FilmFrameSvg} alt='' />
<img className={c.frameImage} src={imageSrc} />
</div>
);
};

export default FilmFrame;
3 changes: 3 additions & 0 deletions apps/web/src/components/FilmFrame/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FilmFrame from './FilmFrame';

export default FilmFrame;
2 changes: 1 addition & 1 deletion apps/web/src/components/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
grid-template-columns: 1fr 1fr 1fr;
align-items: start;
justify-items: start;
z-index: 1000;
z-index: 2;

box-sizing: border-box;
padding: 24px;
Expand Down
60 changes: 60 additions & 0 deletions apps/web/src/components/SpeakersSection/SpeakerCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { SpeakerWithCompanyDto } from '@ddays-app/types';
import FilmFrame from 'components/FilmFrame';
import { motion } from 'framer-motion';
import { useState } from 'react';

import SpeakerModal from './SpeakerModal';
import c from './SpeakersSection.module.scss';

type SpeakerCardProps = {
speaker: SpeakerWithCompanyDto;
width: number;
height: number;
};

const SpeakerCard: React.FC<SpeakerCardProps> = ({
speaker,
width,
height,
}) => {
const [isOpenModal, setIsOpenModal] = useState(false);

const handleOpenModal = () => {
if (!isOpenModal) {
setIsOpenModal(true);
}
};

const handleCloseModal = () => {
setIsOpenModal(false);
};
return (
<>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ ease: 'easeIn', duration: 1 }}
style={{ width: width }}
className={c.card}
onClick={handleOpenModal}>
<FilmFrame imageSrc={speaker.photo} height={height} width={width} />
<div className={c.cardInfoWrapper}>
<h3 className={c.cardName}>
{speaker.firstName} {speaker.lastName}
</h3>
<p className={c.cardTitle}>
{speaker.title}{' '}
{speaker.company?.name ? '@ ' + speaker.company?.name : ''}
</p>
</div>
</motion.div>
<SpeakerModal
close={handleCloseModal}
isOpen={isOpenModal}
speaker={speaker}
/>
</>
);
};

export default SpeakerCard;
94 changes: 94 additions & 0 deletions apps/web/src/components/SpeakersSection/SpeakerModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { SpeakerWithCompanyDto } from '@ddays-app/types';
import FilmFrame from 'components/FilmFrame';
import { useEffect } from 'react';

import CloseSvg from '../../assets/close.svg';
import c from './SpeakersSection.module.scss';

type SpeakerModalProps = {
close: () => void;
isOpen: boolean;
speaker: SpeakerWithCompanyDto;
};

const SpeakerModal: React.FC<SpeakerModalProps> = ({
close,
isOpen,
speaker,
}) => {
useEffect(() => {
if (isOpen) {
const width = document.body.clientWidth;
document.body.style.overflow = 'hidden';
document.body.style.width = `${width}px`;
} else {
document.body.style.overflow = 'visible';
document.body.style.width = `auto`;
}

return () => {
document.body.style.overflow = 'visible';
document.body.style.width = `auto`;
};
}, [isOpen]);

useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
close();
}
};

window.addEventListener('keydown', handleEsc);

return () => {
window.removeEventListener('keydown', handleEsc);
};
}, [close]);

if (!isOpen) {
return null;
}

return (
<div data-lenis-prevent className={c.modalBackground} onClick={close}>
<div className={c.modal}>
<div
data-lenis-prevent
className={c.container}
onClick={(e) => e.stopPropagation()}>
<img src={CloseSvg} alt='Close' className={c.close} onClick={close} />
<div className={c.modalImage}>
<FilmFrame imageSrc={speaker.photo} width={320} height={400} />
</div>
<div className={c.modalRight}>
<h2 className={c.modalSpeakerName}>
{speaker.firstName} {speaker.lastName}
</h2>
<h3 className={c.modalTitle}>
{speaker.title}{' '}
{speaker.company?.name ? '@ ' + speaker.company?.name : ''}
</h3>
<div className={c.socialsWrapper}>
{speaker.linkedin && (
<a className={c.socialsLink} href={speaker.linkedin}>
{`[`} LINKEDIN {']'}
</a>
)}
{speaker.instagram && (
<a href={speaker.instagram}>
{`[`} INSTAGRAM {']'}
</a>
)}
</div>
<section className={c.modalDescription}>
{speaker.description}
</section>
</div>
</div>
</div>
</div>
);
};

export default SpeakerModal;
Loading

0 comments on commit 13f9c3e

Please sign in to comment.