mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-01-17 22:28:32 +01:00
Add video and guides on homepage
This commit is contained in:
parent
0e90d53b8e
commit
74ef38cdb6
@ -5,7 +5,7 @@
|
||||
"url": "/guides/ci-cd",
|
||||
"fileName": "ci-cd",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
@ -15,7 +15,7 @@
|
||||
"url": "/guides/sso",
|
||||
"fileName": "sso",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-07-01T19:59:14.191Z",
|
||||
"createdAt": "2020-07-01T19:59:14.191Z"
|
||||
},
|
||||
@ -25,7 +25,7 @@
|
||||
"url": "/guides/oauth",
|
||||
"fileName": "oauth",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-06-28T19:59:14.191Z",
|
||||
"createdAt": "2020-06-28T19:59:14.191Z"
|
||||
},
|
||||
@ -35,7 +35,7 @@
|
||||
"url": "/guides/jwt-authentication",
|
||||
"fileName": "jwt-authentication",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-06-20T19:59:14.191Z",
|
||||
"createdAt": "2020-06-20T19:59:14.191Z"
|
||||
},
|
||||
@ -45,7 +45,7 @@
|
||||
"url": "/guides/token-authentication",
|
||||
"fileName": "token-authentication",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-06-02T20:59:14.191Z",
|
||||
"createdAt": "2020-06-02T20:59:14.191Z"
|
||||
},
|
||||
@ -55,7 +55,7 @@
|
||||
"url": "/guides/session-authentication",
|
||||
"fileName": "session-authentication",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-05-26T20:59:14.191Z",
|
||||
"createdAt": "2020-05-26T20:59:14.191Z"
|
||||
},
|
||||
@ -65,7 +65,7 @@
|
||||
"url": "/guides/basic-authentication",
|
||||
"fileName": "basic-authentication",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-05-19T20:59:14.191Z",
|
||||
"createdAt": "2020-05-19T20:59:14.191Z"
|
||||
},
|
||||
@ -75,7 +75,7 @@
|
||||
"url": "/guides/character-encodings",
|
||||
"fileName": "character-encodings",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-05-14T20:59:14.191Z",
|
||||
"createdAt": "2020-05-14T20:59:14.191Z"
|
||||
},
|
||||
@ -85,7 +85,7 @@
|
||||
"url": "/guides/unfamiliar-codebase",
|
||||
"fileName": "unfamiliar-codebase",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-05-04T20:59:14.191Z",
|
||||
"createdAt": "2020-05-04T20:59:14.191Z"
|
||||
},
|
||||
@ -95,7 +95,7 @@
|
||||
"url": "/guides/why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"fileName": "why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"isPro": false,
|
||||
"author": "spekulatius",
|
||||
"authorUsername": "spekulatius",
|
||||
"updatedAt": "2020-05-04T12:59:14.191Z",
|
||||
"createdAt": "2020-05-04T12:59:14.191Z"
|
||||
},
|
||||
@ -105,7 +105,7 @@
|
||||
"url": "/guides/dhcp-in-one-picture",
|
||||
"fileName": "dhcp-in-one-picture",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-04-28T15:48:21.191Z",
|
||||
"createdAt": "2020-04-28T15:48:21.191Z"
|
||||
},
|
||||
@ -115,7 +115,7 @@
|
||||
"url": "/guides/ssl-tls-https-ssh",
|
||||
"fileName": "ssl-tls-https-ssh",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-04-22T15:48:21.191Z",
|
||||
"createdAt": "2020-04-22T15:48:21.191Z"
|
||||
},
|
||||
@ -125,7 +125,7 @@
|
||||
"url": "/guides/asymptotic-notation",
|
||||
"fileName": "asymptotic-notation",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-04-03T15:48:21.191Z",
|
||||
"createdAt": "2020-04-03T15:48:21.191Z"
|
||||
},
|
||||
@ -135,7 +135,7 @@
|
||||
"url": "/guides/big-o-notation",
|
||||
"fileName": "big-o-notation",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-03-15T15:48:21.191Z",
|
||||
"createdAt": "2020-03-15T15:48:21.191Z"
|
||||
},
|
||||
@ -145,7 +145,7 @@
|
||||
"url": "/guides/random-numbers",
|
||||
"fileName": "random-numbers",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-03-14T15:48:21.191Z",
|
||||
"createdAt": "2020-03-14T15:48:21.191Z"
|
||||
},
|
||||
@ -155,7 +155,7 @@
|
||||
"url": "/guides/scaling-databases",
|
||||
"fileName": "scaling-databases",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2020-02-18T15:48:21.191Z",
|
||||
"createdAt": "2020-02-18T15:48:21.191Z"
|
||||
},
|
||||
@ -165,7 +165,7 @@
|
||||
"url": "/guides/what-is-internet",
|
||||
"fileName": "what-is-internet",
|
||||
"isPro": false,
|
||||
"author": "dmytrobol",
|
||||
"authorUsername": "dmytrobol",
|
||||
"updatedAt": "2020-02-29T15:48:21.191Z",
|
||||
"createdAt": "2020-02-29T15:48:21.191Z"
|
||||
},
|
||||
@ -175,7 +175,7 @@
|
||||
"url": "/guides/torrent-client",
|
||||
"fileName": "torrent-client",
|
||||
"isPro": false,
|
||||
"author": "jesse",
|
||||
"authorUsername": "jesse",
|
||||
"updatedAt": "2020-01-17T15:48:21.191Z",
|
||||
"createdAt": "2020-01-17T15:48:21.191Z",
|
||||
"canonical": "https://blog.jse.li/posts/torrent/"
|
||||
@ -186,7 +186,7 @@
|
||||
"url": "/guides/levels-of-seniority",
|
||||
"fileName": "levels-of-seniority",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2019-12-03T12:13:00.860Z",
|
||||
"createdAt": "2019-12-03T12:13:00.860Z"
|
||||
},
|
||||
@ -196,7 +196,7 @@
|
||||
"url": "/guides/design-patterns-for-humans",
|
||||
"fileName": "design-patterns-for-humans",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2019-10-09T12:00:00.860Z",
|
||||
"createdAt": "2019-01-23T17:00:00.860Z"
|
||||
},
|
||||
@ -206,7 +206,7 @@
|
||||
"url": "/guides/journey-to-http2",
|
||||
"fileName": "journey-to-http2",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"createdAt": "2018-12-04T12:00:00.860Z",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"isDraft": true
|
||||
@ -217,7 +217,7 @@
|
||||
"url": "/guides/dns-in-one-picture",
|
||||
"fileName": "dns-in-one-picture",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"createdAt": "2018-12-04T17:00:00.860Z"
|
||||
},
|
||||
@ -227,7 +227,7 @@
|
||||
"url": "/guides/http-caching",
|
||||
"fileName": "http-caching",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"createdAt": "2018-11-29T17:00:00.860Z",
|
||||
"updatedAt": "2018-11-29T17:00:00.860Z"
|
||||
},
|
||||
@ -237,7 +237,7 @@
|
||||
"url": "/guides/history-of-javascript",
|
||||
"fileName": "history-of-javascript",
|
||||
"isPro": false,
|
||||
"author": "kamranahmedse",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"createdAt": "2017-10-28T17:00:00.860Z",
|
||||
"updatedAt": "2017-10-28T17:00:00.860Z"
|
||||
},
|
||||
@ -247,7 +247,7 @@
|
||||
"url": "/guides/proxy-servers",
|
||||
"fileName": "proxy-servers",
|
||||
"isPro": false,
|
||||
"author": "ebrahimbharmal007",
|
||||
"authorUsername": "ebrahimbharmal007",
|
||||
"createdAt": "2020-07-24T12:40:18",
|
||||
"updatedAt": "2020-07-24T12:40:18"
|
||||
}
|
||||
|
92
content/videos.json
Normal file
92
content/videos.json
Normal file
@ -0,0 +1,92 @@
|
||||
[
|
||||
{
|
||||
"title": "Transport Protocols: TCP vs UDP",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "OSI Model Explained",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Creating a React App",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "DOM vs Shadow DOM vs Virtual DOM",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Everything you need to know about HTTP Caching",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Content Delivery Networks",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Load Balancers in Depth",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "DNS and How does it Work?",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "JavaScript Fetch API",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/watch/transport-protocols-tcp-vs-udp",
|
||||
"fileName": "tcp-udp",
|
||||
"isPro": false,
|
||||
"duration": "10 minutes",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
}
|
||||
]
|
14
lib/author.ts
Normal file
14
lib/author.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import authors from '../content/authors.json';
|
||||
|
||||
export type AuthorType = {
|
||||
username: string;
|
||||
name: string;
|
||||
twitter: string;
|
||||
picture: string;
|
||||
bio: string;
|
||||
}
|
||||
|
||||
export function findAuthorByUsername(username: string): AuthorType | undefined {
|
||||
return (authors as AuthorType[]).find(author => author.username === username);
|
||||
}
|
||||
|
26
lib/guide.ts
26
lib/guide.ts
@ -1,6 +1,7 @@
|
||||
import guides from '../content/guides.json';
|
||||
import authors from '../content/authors.json';
|
||||
import formatDate from 'date-fns/format';
|
||||
import { NextApiRequest } from 'next';
|
||||
import { AuthorType, findAuthorByUsername } from './author';
|
||||
|
||||
export type GuideType = {
|
||||
title: string;
|
||||
@ -8,12 +9,13 @@ export type GuideType = {
|
||||
url: string;
|
||||
fileName: string;
|
||||
isPro: boolean;
|
||||
author: string;
|
||||
isDraft: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
formattedCreatedAt: string;
|
||||
formattedUpdatedAt: string;
|
||||
authorUsername: string;
|
||||
author?: AuthorType;
|
||||
};
|
||||
|
||||
export function getAllGuides(limit: number = 0): GuideType[] {
|
||||
@ -27,3 +29,23 @@ export function getAllGuides(limit: number = 0): GuideType[] {
|
||||
}))
|
||||
.slice(0, limit ? limit : guides.length);
|
||||
}
|
||||
|
||||
|
||||
export function getRequestedGuide(req: NextApiRequest): GuideType | undefined {
|
||||
const allGuides = getAllGuides();
|
||||
const guide = allGuides.find(guide => guide.url === req.url);
|
||||
if (!guide) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
...guide,
|
||||
author: findAuthorByUsername(guide.authorUsername)
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
44
lib/video.ts
Normal file
44
lib/video.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import videos from '../content/videos.json';
|
||||
import formatDate from 'date-fns/format';
|
||||
import { NextApiRequest } from 'next';
|
||||
|
||||
export type VideoType = {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
fileName: string;
|
||||
isPro: boolean;
|
||||
duration: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
formattedCreatedAt: string;
|
||||
formattedUpdatedAt: string;
|
||||
};
|
||||
|
||||
export function getAllVideos(limit: number = 0): VideoType[] {
|
||||
return (videos as VideoType[])
|
||||
.sort((a, b) => (new Date(b.updatedAt) as any) - (new Date(a.updatedAt) as any))
|
||||
.map(video => ({
|
||||
...video,
|
||||
formattedCreatedAt: formatDate(new Date(video.createdAt), 'MMMM d, yyyy'),
|
||||
formattedUpdatedAt: formatDate(new Date(video.updatedAt), 'MMMM d, yyyy')
|
||||
}))
|
||||
.slice(0, limit ? limit : videos.length);
|
||||
}
|
||||
|
||||
|
||||
export function getRequestedGuide(req: NextApiRequest): VideoType | undefined {
|
||||
const allVideos = getAllVideos();
|
||||
const video = allVideos.find(video => video.url === req.url);
|
||||
if (!video) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return video;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@ -10,14 +10,16 @@ import { LinksList } from '../components/links-list';
|
||||
import { HomeRoadmapItem } from './roadmaps/components/home-roadmap-item';
|
||||
import { getFeaturedRoadmaps, RoadmapType } from '../lib/roadmap';
|
||||
import { getAllGuides, GuideType } from '../lib/guide';
|
||||
import { getAllVideos, VideoType } from '../lib/video';
|
||||
|
||||
type HomeProps = {
|
||||
roadmaps: RoadmapType[];
|
||||
guides: GuideType[];
|
||||
videos: VideoType[];
|
||||
}
|
||||
|
||||
export default function Home(props: HomeProps) {
|
||||
const { roadmaps, guides } = props;
|
||||
const { roadmaps, guides, videos } = props;
|
||||
|
||||
return (
|
||||
<Box bg='white' minH='100vh'>
|
||||
@ -77,31 +79,24 @@ export default function Home(props: HomeProps) {
|
||||
</Box>
|
||||
|
||||
<LinksList>
|
||||
<LinksListItem hideSubtitleOnMobile title='Transport Protocols: TCP vs UDP' subtitle='15 minutes'
|
||||
icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='OSI Model Explained' subtitle='10 minutes' icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='Creating a React App' badgeText='pro' subtitle='15 minutes'
|
||||
icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='DOM vs Shadow DOM vs Virtual DOM' badgeText='pro'
|
||||
subtitle='15 minutes'
|
||||
icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='Everything you need to know about HTTP caching' badgeText='pro'
|
||||
subtitle='10 minutes'
|
||||
icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='Content Delivery Networks' subtitle='5 minutes' icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='Load Balancers in Depth' subtitle='15 minutes' icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='DNS and how does it work?' subtitle='2 minutes' icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
<LinksListItem hideSubtitleOnMobile title='JavaScript Fetch API' subtitle='22 minutes' icon={<VideoIcon
|
||||
style={{ marginRight: '7px', width: '18px', height: '18px', color: '#9c9c9c' }} />} />
|
||||
|
||||
{videos.map(video => (
|
||||
<LinksListItem
|
||||
key={video.url}
|
||||
hideSubtitleOnMobile
|
||||
title={video.title}
|
||||
subtitle={video.duration}
|
||||
icon={
|
||||
<VideoIcon
|
||||
style={{
|
||||
marginRight: '7px',
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
color: '#9c9c9c'
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<DimmedMore text={'View all Videos'} />
|
||||
</LinksList>
|
||||
</Container>
|
||||
@ -118,7 +113,8 @@ export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
roadmaps: getFeaturedRoadmaps(),
|
||||
guides: getAllGuides(10)
|
||||
guides: getAllGuides(10),
|
||||
videos: getAllVideos(10)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user