commit
3a94049595
6 changed files with 315 additions and 126 deletions
|
|
@ -1,120 +1,197 @@
|
||||||
.PartyDetails {
|
.DetailsWrapper {
|
||||||
display: none; // This breaks transition, find a workaround
|
display: flex;
|
||||||
opacity: 0;
|
flex-direction: column;
|
||||||
margin: $unit-4x auto 0;
|
|
||||||
max-width: $unit * 94;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.Editable {
|
.PartyDetails {
|
||||||
top: $unit;
|
display: none;
|
||||||
height: 0;
|
margin: 0 auto;
|
||||||
z-index: 2;
|
max-width: $unit * 94;
|
||||||
transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out;
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&.Visible {
|
&.Visible {
|
||||||
display: flex;
|
margin-bottom: $unit-12x;
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
&.Editable {
|
||||||
display: block;
|
gap: $unit;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
textarea {
|
&.Visible {
|
||||||
min-height: $unit * 20;
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: $unit * 20;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectTrigger {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: $unit;
|
|
||||||
margin-bottom: $unit-12x;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: $unit;
|
gap: $unit;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ReadOnly {
|
.left {
|
||||||
top: $unit * -1;
|
flex-grow: 1;
|
||||||
transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out;
|
}
|
||||||
|
|
||||||
&.Visible {
|
.right {
|
||||||
display: block;
|
display: flex;
|
||||||
height: auto;
|
flex-direction: row;
|
||||||
opacity: 1;
|
gap: $unit;
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-regular;
|
|
||||||
line-height: $font-regular * 1.2;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: $font-xlarge;
|
|
||||||
font-weight: $normal;
|
|
||||||
text-align: left;
|
|
||||||
margin-bottom: $unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: $unit;
|
|
||||||
margin-bottom: $unit * 2;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: var(--text-primary);
|
|
||||||
|
|
||||||
&.empty {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attribution {
|
&.ReadOnly {
|
||||||
align-items: center;
|
&.Visible {
|
||||||
display: flex;
|
display: block;
|
||||||
flex-direction: row;
|
}
|
||||||
|
|
||||||
& > div {
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: $font-regular * 1.2;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.YoutubeWrapper {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
margin: $unit 0;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
contain: content;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
|
||||||
|
/* gradient */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
|
||||||
|
background-position: top;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
height: 60px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* responsive iframe with a 16:9 aspect ratio
|
||||||
|
thanks https://css-tricks.com/responsive-iframes/
|
||||||
|
*/
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
padding-bottom: calc(100% / (16 / 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > .PlayerButton {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Play button */
|
||||||
|
& > .PlayerButton {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
background-image: url('/icons/youtube.svg');
|
||||||
|
width: 68px;
|
||||||
|
height: 68px;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .PlayerButton,
|
||||||
|
& > .PlayerButton:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post-click styles */
|
||||||
|
&.lyt-activated {
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
&.lyt-activated::before,
|
||||||
|
&.lyt-activated > .PlayerButton {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.PartyInfo {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: $unit * 2;
|
||||||
|
max-width: $unit * 94;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.Left {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: $font-xlarge;
|
||||||
|
font-weight: $normal;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: $unit;
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribution {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
font-size: $font-small;
|
flex-direction: row;
|
||||||
height: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
time {
|
& > div {
|
||||||
font-size: $font-small;
|
align-items: center;
|
||||||
}
|
display: inline-flex;
|
||||||
|
font-size: $font-small;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
& > *:not(:last-child):after {
|
time {
|
||||||
content: ' · ';
|
font-size: $font-small;
|
||||||
margin: 0 calc($unit / 2);
|
}
|
||||||
|
|
||||||
|
& > *:not(:last-child):after {
|
||||||
|
content: ' · ';
|
||||||
|
margin: 0 calc($unit / 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,13 +224,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.EmptyDetails {
|
|
||||||
display: none;
|
|
||||||
justify-content: center;
|
|
||||||
margin: $unit-4x 0 $unit-10x;
|
|
||||||
|
|
||||||
&.Visible {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import Linkify from 'react-linkify'
|
import Linkify from 'react-linkify'
|
||||||
|
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import reactStringReplace from 'react-string-replace'
|
||||||
|
|
||||||
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||||
|
|
||||||
|
|
@ -23,6 +25,7 @@ import CrossIcon from '~public/icons/Cross.svg'
|
||||||
import EditIcon from '~public/icons/Edit.svg'
|
import EditIcon from '~public/icons/Edit.svg'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import { youtube } from '~utils/youtube'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -47,11 +50,13 @@ const PartyDetails = (props: Props) => {
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [raidSlug, setRaidSlug] = useState('')
|
const [raidSlug, setRaidSlug] = useState('')
|
||||||
|
const [embeddedDescription, setEmbeddedDescription] =
|
||||||
|
useState<React.ReactNode>()
|
||||||
|
|
||||||
const readOnlyClasses = classNames({
|
const readOnlyClasses = classNames({
|
||||||
PartyDetails: true,
|
PartyDetails: true,
|
||||||
ReadOnly: true,
|
ReadOnly: true,
|
||||||
Visible: true,
|
Visible: !open,
|
||||||
})
|
})
|
||||||
|
|
||||||
const editableClasses = classNames({
|
const editableClasses = classNames({
|
||||||
|
|
@ -102,6 +107,45 @@ const PartyDetails = (props: Props) => {
|
||||||
setErrors(newErrors)
|
setErrors(newErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Extract the video IDs from the description
|
||||||
|
if (party.description) {
|
||||||
|
const videoIds = extractYoutubeVideoIds(party.description)
|
||||||
|
|
||||||
|
// Fetch the video titles for each ID
|
||||||
|
const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))
|
||||||
|
|
||||||
|
// Wait for all the video titles to be fetched
|
||||||
|
Promise.all(fetchPromises).then((videoTitles) => {
|
||||||
|
// YouTube regex
|
||||||
|
const youtubeUrlRegex =
|
||||||
|
/https:\/\/www\.youtube\.com\/watch\?v=([\w-]+)/g
|
||||||
|
// Replace the video URLs in the description with LiteYoutubeEmbed elements
|
||||||
|
const newDescription = reactStringReplace(
|
||||||
|
party.description,
|
||||||
|
youtubeUrlRegex,
|
||||||
|
(match, i) => (
|
||||||
|
<LiteYouTubeEmbed
|
||||||
|
id={match}
|
||||||
|
title={videoTitles[i]}
|
||||||
|
wrapperClass="YoutubeWrapper"
|
||||||
|
playerClass="PlayerButton"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the state with the new description
|
||||||
|
setEmbeddedDescription(newDescription)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [party.description])
|
||||||
|
|
||||||
|
async function fetchYoutubeData(videoId: string) {
|
||||||
|
return await youtube
|
||||||
|
.getVideoById(videoId, { maxResults: 1 })
|
||||||
|
.then((data) => data.items[0].snippet.localized.title)
|
||||||
|
}
|
||||||
|
|
||||||
function toggleDetails() {
|
function toggleDetails() {
|
||||||
setOpen(!open)
|
setOpen(!open)
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +163,31 @@ const PartyDetails = (props: Props) => {
|
||||||
toggleDetails()
|
toggleDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractYoutubeVideoIds(text: string) {
|
||||||
|
// Create a regular expression to match Youtube URLs in the text
|
||||||
|
const youtubeUrlRegex = /https:\/\/www\.youtube\.com\/watch\?v=([\w-]+)/g
|
||||||
|
|
||||||
|
// Initialize an array to store the video IDs
|
||||||
|
const videoIds = []
|
||||||
|
|
||||||
|
// Use the regular expression to find all the Youtube URLs in the text
|
||||||
|
let match
|
||||||
|
while ((match = youtubeUrlRegex.exec(text)) !== null) {
|
||||||
|
// Extract the video ID from the URL
|
||||||
|
const videoId = match[1]
|
||||||
|
|
||||||
|
// Add the video ID to the array, along with the character position of the URL
|
||||||
|
videoIds.push({
|
||||||
|
id: videoId,
|
||||||
|
url: match[0],
|
||||||
|
position: match.index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the array of video IDs
|
||||||
|
return videoIds
|
||||||
|
}
|
||||||
|
|
||||||
const userImage = (picture?: string, element?: string) => {
|
const userImage = (picture?: string, element?: string) => {
|
||||||
if (picture && element)
|
if (picture && element)
|
||||||
return (
|
return (
|
||||||
|
|
@ -268,9 +337,13 @@ const PartyDetails = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const readOnly = (
|
const readOnly = (
|
||||||
<section className={readOnlyClasses}>
|
<section className={readOnlyClasses}>{embeddedDescription}</section>
|
||||||
<div className="info">
|
)
|
||||||
<div className="left">
|
|
||||||
|
return (
|
||||||
|
<section className="DetailsWrapper">
|
||||||
|
<div className="PartyInfo">
|
||||||
|
<div className="Left">
|
||||||
<h1 className={!party.name ? 'empty' : ''}>
|
<h1 className={!party.name ? 'empty' : ''}>
|
||||||
{party.name ? party.name : 'Untitled'}
|
{party.name ? party.name : 'Untitled'}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -289,7 +362,7 @@ const PartyDetails = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="right">
|
<div className="Right">
|
||||||
{party.editable ? (
|
{party.editable ? (
|
||||||
<Button
|
<Button
|
||||||
accessoryIcon={<EditIcon />}
|
accessoryIcon={<EditIcon />}
|
||||||
|
|
@ -301,21 +374,9 @@ const PartyDetails = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{party.description ? (
|
|
||||||
<p>
|
|
||||||
<Linkify>{party.description}</Linkify>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{readOnly}
|
{readOnly}
|
||||||
{editable}
|
{editable}
|
||||||
</React.Fragment>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
43
package-lock.json
generated
43
package-lock.json
generated
|
|
@ -33,9 +33,12 @@
|
||||||
"react-i18next": "^11.15.5",
|
"react-i18next": "^11.15.5",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
"react-linkify": "^1.0.0-alpha",
|
||||||
|
"react-lite-youtube-embed": "^2.3.52",
|
||||||
"react-scroll": "^1.8.5",
|
"react-scroll": "^1.8.5",
|
||||||
|
"react-string-replace": "^1.1.0",
|
||||||
"sass": "^1.49.0",
|
"sass": "^1.49.0",
|
||||||
"valtio": "^1.3.0"
|
"valtio": "^1.3.0",
|
||||||
|
"youtube-api-v3-wrapper": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
|
|
@ -6902,6 +6905,15 @@
|
||||||
"tlds": "^1.199.0"
|
"tlds": "^1.199.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-lite-youtube-embed": {
|
||||||
|
"version": "2.3.52",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lite-youtube-embed/-/react-lite-youtube-embed-2.3.52.tgz",
|
||||||
|
"integrity": "sha512-G010PvCavA4EqL8mZ/Sv9XXiHnjMfONW+lmNeCRnSEPluPdptv2lZ0cNlngrj7K9j7luc8pbpyrmNpKbD9VMmw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.0.8",
|
||||||
|
"react-dom": ">=16.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||||
|
|
@ -6978,6 +6990,14 @@
|
||||||
"react-dom": "^15.5.4 || ^16.0.0 || ^17.0.0"
|
"react-dom": "^15.5.4 || ^16.0.0 || ^17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-string-replace": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-N6RalSDFGbOHs0IJi1H611WbZsvk3ZT47Jl2JEXFbiS3kTwsdCYij70Keo/tWtLy7sfhDsYm7CwNM/WmjXIaMw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||||
|
|
@ -7889,6 +7909,11 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/youtube-api-v3-wrapper": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/youtube-api-v3-wrapper/-/youtube-api-v3-wrapper-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-/c4B0BSe2BEElGHO/VJt4KCqrScl7R7xG44BugvuGsBciP+fF03JN7gS/X0jnaGHOnng7GP1n320hDjwquZOgA=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -12843,6 +12868,12 @@
|
||||||
"tlds": "^1.199.0"
|
"tlds": "^1.199.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-lite-youtube-embed": {
|
||||||
|
"version": "2.3.52",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lite-youtube-embed/-/react-lite-youtube-embed-2.3.52.tgz",
|
||||||
|
"integrity": "sha512-G010PvCavA4EqL8mZ/Sv9XXiHnjMfONW+lmNeCRnSEPluPdptv2lZ0cNlngrj7K9j7luc8pbpyrmNpKbD9VMmw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||||
|
|
@ -12892,6 +12923,11 @@
|
||||||
"prop-types": "^15.7.2"
|
"prop-types": "^15.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-string-replace": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-N6RalSDFGbOHs0IJi1H611WbZsvk3ZT47Jl2JEXFbiS3kTwsdCYij70Keo/tWtLy7sfhDsYm7CwNM/WmjXIaMw=="
|
||||||
|
},
|
||||||
"react-style-singleton": {
|
"react-style-singleton": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||||
|
|
@ -13524,6 +13560,11 @@
|
||||||
"version": "1.10.2",
|
"version": "1.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||||
|
},
|
||||||
|
"youtube-api-v3-wrapper": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/youtube-api-v3-wrapper/-/youtube-api-v3-wrapper-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-/c4B0BSe2BEElGHO/VJt4KCqrScl7R7xG44BugvuGsBciP+fF03JN7gS/X0jnaGHOnng7GP1n320hDjwquZOgA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,12 @@
|
||||||
"react-i18next": "^11.15.5",
|
"react-i18next": "^11.15.5",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
"react-linkify": "^1.0.0-alpha",
|
||||||
|
"react-lite-youtube-embed": "^2.3.52",
|
||||||
"react-scroll": "^1.8.5",
|
"react-scroll": "^1.8.5",
|
||||||
|
"react-string-replace": "^1.1.0",
|
||||||
"sass": "^1.49.0",
|
"sass": "^1.49.0",
|
||||||
"valtio": "^1.3.0"
|
"valtio": "^1.3.0",
|
||||||
|
"youtube-api-v3-wrapper": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
|
|
|
||||||
11
public/icons/youtube.svg
Normal file
11
public/icons/youtube.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1384_2987)">
|
||||||
|
<path d="M66.52 17.74C65.74 14.81 64.03 12.33 61.1 11.55C55.79 10.13 34 10 34 10C34 10 12.21 10.13 6.9 11.55C3.97 12.33 2.27 14.81 1.48 17.74C0.0600001 23.05 0 34 0 34C0 34 0.0600001 44.95 1.48 50.26C2.26 53.19 3.97 55.67 6.9 56.45C12.21 57.87 34 58 34 58C34 58 55.79 57.87 61.1 56.45C64.03 55.67 65.74 53.19 66.52 50.26C67.94 44.95 68 34 68 34C68 34 67.94 23.05 66.52 17.74Z" fill="#FF0000"/>
|
||||||
|
<path d="M45 34L27 24V44" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1384_2987">
|
||||||
|
<rect width="68" height="68" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 684 B |
6
utils/youtube.tsx
Normal file
6
utils/youtube.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { YoutubeAPIClient } from 'youtube-api-v3-wrapper'
|
||||||
|
|
||||||
|
export const youtube = new YoutubeAPIClient(
|
||||||
|
'key',
|
||||||
|
process.env.NEXT_PUBLIC_YOUTUBE_API_KEY
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue