Compare commits
6 Commits
ea603804a2
...
4afe2f906d
Author | SHA1 | Date | |
---|---|---|---|
4afe2f906d | |||
b87df50328 | |||
582455933e | |||
a408abdb97 | |||
f4eadc34c7 | |||
38f3caa524 |
@ -1,38 +0,0 @@
|
|||||||
import { ArrowLeftIcon } from '@heroicons/react/24/outline';
|
|
||||||
|
|
||||||
import Button from './Button';
|
|
||||||
import NavBar from './NavBar';
|
|
||||||
import SideNavChannel from './SideNavChannel';
|
|
||||||
import streamData from '../placeholder/GetStreams';
|
|
||||||
import { NextPage } from 'next';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
const BrowseLayout: NextPage = ({children}) => {
|
|
||||||
return (
|
|
||||||
<div className="font-inter flex flex-col h-screen text-gray-100">
|
|
||||||
<NavBar />
|
|
||||||
<main className="flex-1 flex flex-row overflow-hidden">
|
|
||||||
<div className="bg-neutral-800 w-60 flex flex-col">
|
|
||||||
<div className="flex flex-row justify-between p-2 items-center">
|
|
||||||
<p className="uppercase font-semibold text-sm">Trending channels</p>
|
|
||||||
<Button variant="subtle" className="p-2">
|
|
||||||
<ArrowLeftIcon className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<ul className="flex-1 overflow-scrollbar">
|
|
||||||
{streamData.data.map((stream) => (
|
|
||||||
<li key={stream.id}>
|
|
||||||
<Link href={`/${stream.user_login}`} passHref={true}>
|
|
||||||
<SideNavChannel stream={stream} />
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BrowseLayout;
|
|
@ -1,65 +0,0 @@
|
|||||||
import { UserIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { FC, useState } from 'react';
|
|
||||||
|
|
||||||
import Button from './Button';
|
|
||||||
import Input from './Input';
|
|
||||||
import LoginModal from './LoginModal';
|
|
||||||
|
|
||||||
const NavBar: FC = () => {
|
|
||||||
const [showLogin, setShowLogin] = useState(false);
|
|
||||||
const [showTab, setShowTab] = useState(0);
|
|
||||||
|
|
||||||
const showLoginTab = () => {
|
|
||||||
setShowTab(0);
|
|
||||||
setShowLogin(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showSignupTab = () => {
|
|
||||||
setShowTab(1);
|
|
||||||
setShowLogin(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="bg-zinc-800 w-screen font-semibold border-b border-b-black">
|
|
||||||
<div className="flex flex-row justify-between items-center mx-2">
|
|
||||||
<div className="basis-1/4">
|
|
||||||
<ul className="flex flex-row space-x-8 items-center">
|
|
||||||
<li>
|
|
||||||
<img src="./assets/images/logo.png" className="w-8 h-8" alt="logo" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p className="text-lg">Browse</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="basis-2/4">
|
|
||||||
<div className="flex flex-row space-x-3 items-center justify-center">
|
|
||||||
<Input className=" w-72 my-2 p-2" placeholder="Search" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="basis-1/4">
|
|
||||||
<ul className="justify-end flex flex-row space-x-3 items-center">
|
|
||||||
<li>
|
|
||||||
<Button className="text-sm px-3 py-2 bg-neutral-700" onClick={showLoginTab}>
|
|
||||||
Log In
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Button className="text-sm px-3 py-2 bg-violet-500" onClick={showSignupTab}>
|
|
||||||
Sign Up
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Button variant="subtle" className="p-1">
|
|
||||||
<UserIcon className="h-5 w-5 inline-block" />
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<LoginModal isOpen={showLogin} defaultPage={showTab} onClose={() => setShowLogin(false)} />
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NavBar;
|
|
@ -1,28 +0,0 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
import { Stream } from '../types';
|
|
||||||
import { numFormatter } from '../utils/format';
|
|
||||||
|
|
||||||
interface SideNavChannelProps {
|
|
||||||
stream: Stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SideNavChannel: FC<SideNavChannelProps> = ({ stream }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row px-3 py-2 text-sm leading-4 space-x-2 hover:bg-neutral-700/40 cursor-pointer">
|
|
||||||
<img className="rounded-full w-8 h-8" src={stream.thumbnail_url} alt="avatar" />
|
|
||||||
<div className="flex flex-col flex-1">
|
|
||||||
<div className="flex flex-row justify-between">
|
|
||||||
<div className="font-bold">{stream.user_name}</div>
|
|
||||||
<div className="space-x-1 flex flex-row items-center">
|
|
||||||
<div className="w-2 h-2 bg-red-600 rounded-full inline-block" />
|
|
||||||
<span>{numFormatter.format(stream.viewer_count)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-300">{stream.game_name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SideNavChannel;
|
|
11
client/components/common/Logo.tsx
Normal file
11
client/components/common/Logo.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { FC } from "react"
|
||||||
|
|
||||||
|
interface LogoProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Logo: FC<LogoProps> = ({ className }) => {
|
||||||
|
return <img src="./assets/images/logo.png" className={className} alt="logo" />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logo
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { forwardRef, ReactNode } from "react"
|
import { forwardRef, ReactNode } from "react"
|
||||||
|
|
||||||
import Input from "./Input"
|
import Input from "../Input"
|
||||||
|
|
||||||
interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
||||||
label?: string
|
label?: string
|
13
client/components/layout/BrowseLayout.tsx
Normal file
13
client/components/layout/BrowseLayout.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import NavBar from "../nav/NavBar"
|
||||||
|
import { NextPage } from "next"
|
||||||
|
|
||||||
|
const BrowseLayout: NextPage = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="font-inter flex flex-col h-screen text-gray-100">
|
||||||
|
<NavBar />
|
||||||
|
<main className="flex-1 flex flex-row overflow-hidden">{children}</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BrowseLayout
|
@ -1,30 +1,30 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from "react"
|
||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
|
||||||
import FormField from './FormField';
|
import FormField from "../common/form/FormField"
|
||||||
import InlineLink from './InlineLink';
|
import InlineLink from "../common/InlineLink"
|
||||||
import SubmitButton from './SubmitButton';
|
import SubmitButton from "../common/form/SubmitButton"
|
||||||
|
|
||||||
interface LoginFormValues {
|
interface LoginFormValues {
|
||||||
username: string;
|
username: string
|
||||||
password: string;
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoginForm: FC = () => {
|
const LoginForm: FC = () => {
|
||||||
const { register, handleSubmit } = useForm<LoginFormValues>();
|
const { register, handleSubmit } = useForm<LoginFormValues>()
|
||||||
const onSubmit: SubmitHandler<LoginFormValues> = (data) => console.log(data);
|
const onSubmit: SubmitHandler<LoginFormValues> = (data) => console.log(data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
<FormField
|
||||||
label="Username"
|
label="Username"
|
||||||
className="py-2 px-2 outline-2 w-full"
|
className="py-2 px-2 outline-2 w-full"
|
||||||
{...register('username')}
|
{...register("username")}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
{...register('password')}
|
{...register("password")}
|
||||||
className="py-2 px-2 outline-2 w-full"
|
className="py-2 px-2 outline-2 w-full"
|
||||||
bottomElement={
|
bottomElement={
|
||||||
<InlineLink to="#" className="block mt-2">
|
<InlineLink to="#" className="block mt-2">
|
||||||
@ -34,7 +34,7 @@ const LoginForm: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<SubmitButton className="w-full" value="Log In" />
|
<SubmitButton className="w-full" value="Log In" />
|
||||||
</form>
|
</form>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LoginForm;
|
export default LoginForm
|
@ -1,33 +1,42 @@
|
|||||||
import { Dialog, Tab } from '@headlessui/react';
|
import { Dialog, Tab } from "@headlessui/react"
|
||||||
import { FC, useEffect, useRef } from 'react';
|
import { FC } from "react"
|
||||||
|
import Logo from "../common/Logo"
|
||||||
|
|
||||||
import LoginForm from './LoginForm';
|
import LoginForm from "./LoginForm"
|
||||||
import LoginModalTab from './LoginModalTab';
|
import LoginModalTab from "./LoginModalTab"
|
||||||
import SignupForm from './SignupForm';
|
import SignupForm from "./SignupForm"
|
||||||
|
|
||||||
export interface LoginModelProps {
|
export interface LoginModelProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean
|
||||||
onClose: () => any;
|
onClose?: () => any
|
||||||
defaultPage?: number;
|
defaultPage?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoginModal: FC<LoginModelProps> = ({ defaultPage, isOpen, onClose }) => {
|
const LoginModal: FC<LoginModelProps> = ({
|
||||||
|
defaultPage,
|
||||||
|
isOpen,
|
||||||
|
onClose = (b: boolean) => {},
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||||
<div className="bg-black/80 fixed inset-0 flex items-center justify-center">
|
<div className="bg-black/80 fixed inset-0 flex items-center justify-center">
|
||||||
<Dialog.Panel className="bg-zinc-900 text-gray-100 w-[420px] rounded-md py-12 px-6">
|
<Dialog.Panel className="bg-zinc-900 text-gray-100 w-[420px] rounded-md py-12 px-6">
|
||||||
<div className="flex flex-row items-center justify-center">
|
<div className="flex flex-row items-center justify-center">
|
||||||
<Dialog.Title className="text-xl">
|
<Dialog.Title className="text-xl">
|
||||||
<img src="./assets/images/logo.png" className="inline w-12 h-12" alt="logo" /> Log in to twitch-clone
|
<Logo className="inline w-12 h-12" /> Log in to twitch-clone
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
<Tab.Group defaultIndex={defaultPage}>
|
<Tab.Group defaultIndex={defaultPage}>
|
||||||
<Tab.List className="space-x-4 border-b border-b-neutral-100/40 mt-4">
|
<Tab.List className="space-x-4 border-b border-b-neutral-100/40 mt-4">
|
||||||
<Tab>
|
<Tab>
|
||||||
{({ selected }) => <LoginModalTab selected={selected}>Log In</LoginModalTab>}
|
{({ selected }) => (
|
||||||
|
<LoginModalTab selected={selected}>Log In</LoginModalTab>
|
||||||
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
{({ selected }) => <LoginModalTab selected={selected}>Sign Up</LoginModalTab>}
|
{({ selected }) => (
|
||||||
|
<LoginModalTab selected={selected}>Sign Up</LoginModalTab>
|
||||||
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tab.List>
|
</Tab.List>
|
||||||
<Tab.Panels className="mt-4">
|
<Tab.Panels className="mt-4">
|
||||||
@ -41,7 +50,8 @@ const LoginModal: FC<LoginModelProps> = ({ defaultPage, isOpen, onClose }) => {
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>)
|
</Dialog>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default LoginModal;
|
export default LoginModal
|
@ -1,19 +1,14 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { SelfServiceRegistrationFlow } from "@ory/client"
|
import { FC } from "react"
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { useMemo } from "react"
|
|
||||||
import { FC, useEffect, useState } from "react"
|
|
||||||
import { SubmitHandler, useForm } from "react-hook-form"
|
import { SubmitHandler, useForm } from "react-hook-form"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import ory from "../services/ory"
|
|
||||||
|
|
||||||
import FormField from "./FormField"
|
import FormField from "../common/form/FormField"
|
||||||
import InlineLink from "./InlineLink"
|
import InlineLink from "../common/InlineLink"
|
||||||
import Input from "./Input"
|
import Input from "../common/Input"
|
||||||
import SubmitButton from "./SubmitButton"
|
import SubmitButton from "../common/form/SubmitButton"
|
||||||
|
import { PASSWORD_REGEX } from "../../config"
|
||||||
const PASSWORD_REGEX =
|
import useSignUpFlow from "../../hooks/useSignUpFlow"
|
||||||
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/
|
|
||||||
|
|
||||||
const SignupFormSchema = z
|
const SignupFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -48,71 +43,35 @@ const formFields = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const SignupForm: FC = () => {
|
const SignupForm: FC = () => {
|
||||||
const router = useRouter()
|
const signUpFlow = useSignUpFlow()
|
||||||
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
|
||||||
const { flow: flowId, return_to: returnTo } = router.query
|
|
||||||
const { register, handleSubmit, formState } = useForm<SignupFormValues>({
|
const { register, handleSubmit, formState } = useForm<SignupFormValues>({
|
||||||
resolver: zodResolver(SignupFormSchema),
|
resolver: zodResolver(SignupFormSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const func = async () => {
|
|
||||||
if (!router.isReady || flow) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceFlow
|
|
||||||
if (flowId) {
|
|
||||||
serviceFlow = await ory.getSelfServiceRegistrationFlow(String(flowId))
|
|
||||||
} else {
|
|
||||||
serviceFlow =
|
|
||||||
await ory.initializeSelfServiceRegistrationFlowForBrowsers(
|
|
||||||
returnTo ? String(returnTo) : undefined,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setFlow(serviceFlow.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func()
|
|
||||||
}, [flowId, router, router.isReady, returnTo, flow])
|
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<SignupFormValues> = async (data) => {
|
const onSubmit: SubmitHandler<SignupFormValues> = async (data) => {
|
||||||
await router.push(`/signup?flow=${flow?.id}`, undefined, {
|
await signUpFlow.submitData({
|
||||||
shallow: true,
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
const resp = await ory.submitSelfServiceRegistrationFlow(
|
|
||||||
String(flow?.id),
|
|
||||||
{
|
|
||||||
csrf_token: data.csrfToken,
|
csrf_token: data.csrfToken,
|
||||||
method: "password",
|
method: "password",
|
||||||
password: data.password,
|
password: data.password,
|
||||||
traits: {
|
traits: {
|
||||||
email: data.email,
|
email: data.email,
|
||||||
// username: data.username,
|
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
)
|
|
||||||
console.log(resp)
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
{flow && (
|
|
||||||
<Input
|
|
||||||
hidden={true}
|
|
||||||
value={flow.ui.nodes[0].attributes.value}
|
|
||||||
{...register("csrfToken")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
Creating an account allows you to participate in chat, follow your
|
Creating an account allows you to participate in chat, follow your
|
||||||
favorite channels, and broadcast from your own channel.
|
favorite channels, and broadcast from your own channel.
|
||||||
</p>
|
</p>
|
||||||
|
{signUpFlow.flow && (
|
||||||
|
<Input
|
||||||
|
hidden={true}
|
||||||
|
value={signUpFlow.flow.ui.nodes[0].attributes.value}
|
||||||
|
{...register("csrfToken")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{formFields.map((field) => (
|
{formFields.map((field) => (
|
||||||
<FormField
|
<FormField
|
||||||
key={field.id}
|
key={field.id}
|
||||||
@ -135,7 +94,11 @@ const SignupForm: FC = () => {
|
|||||||
</InlineLink>
|
</InlineLink>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<SubmitButton className="w-full" value="Sign Up" />
|
<SubmitButton
|
||||||
|
disabled={!signUpFlow.flow}
|
||||||
|
className="w-full"
|
||||||
|
value="Sign Up"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from "react"
|
||||||
|
|
||||||
import ChatBadge from './ChatBadge';
|
import ChatBadge from "./ChatBadge"
|
||||||
|
|
||||||
const ChatMessage: FC = () => {
|
const ChatMessage: FC = () => {
|
||||||
return (
|
return (
|
||||||
@ -19,7 +19,7 @@ const ChatMessage: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ChatMessage;
|
export default ChatMessage
|
70
client/components/nav/NavBar.tsx
Normal file
70
client/components/nav/NavBar.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { UserIcon } from "@heroicons/react/24/outline"
|
||||||
|
import { FC, useState } from "react"
|
||||||
|
|
||||||
|
import Button from "../common/Button"
|
||||||
|
import Logo from "../common/Logo"
|
||||||
|
import LoginModal, { LoginModelProps } from "../login/LoginModal"
|
||||||
|
|
||||||
|
const NavBar: FC = () => {
|
||||||
|
const [modalProps, setModalProps] = useState<LoginModelProps>({
|
||||||
|
isOpen: false,
|
||||||
|
defaultPage: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const showLoginTab = () =>
|
||||||
|
setModalProps({
|
||||||
|
defaultPage: 0,
|
||||||
|
isOpen: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const showSignupTab = () =>
|
||||||
|
setModalProps({
|
||||||
|
defaultPage: 1,
|
||||||
|
isOpen: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="bg-zinc-800 w-screen font-semibold border-b border-b-black">
|
||||||
|
<div className="flex flex-row justify-between items-center h-12 mx-2">
|
||||||
|
<div>
|
||||||
|
<ul className="flex flex-row space-x-8 items-center">
|
||||||
|
<li>
|
||||||
|
<Logo className="w-8 h-8" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul className="justify-end flex flex-row space-x-3 items-center">
|
||||||
|
<li>
|
||||||
|
<Button
|
||||||
|
className="text-sm px-3 py-2 bg-neutral-700"
|
||||||
|
onClick={showLoginTab}
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Button
|
||||||
|
className="text-sm px-3 py-2 bg-violet-500"
|
||||||
|
onClick={showSignupTab}
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Button variant="subtle" className="p-[0.4rem]">
|
||||||
|
<UserIcon className="h-5 w-5 inline-block" />
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LoginModal
|
||||||
|
{...modalProps}
|
||||||
|
onClose={() => setModalProps((old) => ({ ...old, isOpen: false }))}
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavBar
|
@ -1 +1,3 @@
|
|||||||
export const KRATOS_URL = "http://127.0.0.1:4433"
|
export const KRATOS_URL = "http://127.0.0.1:4433"
|
||||||
|
export const PASSWORD_REGEX =
|
||||||
|
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/
|
||||||
|
54
client/hooks/useSignUpFlow.ts
Normal file
54
client/hooks/useSignUpFlow.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
SelfServiceRegistrationFlow,
|
||||||
|
SubmitSelfServiceRegistrationFlowBody,
|
||||||
|
} from "@ory/client"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import ory from "../services/ory"
|
||||||
|
|
||||||
|
export const useSignUpFlow = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
||||||
|
const { flow: flowId, return_to: returnTo } = router.query
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const func = async () => {
|
||||||
|
if (!router.isReady || flow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceFlow
|
||||||
|
if (flowId) {
|
||||||
|
serviceFlow = await ory.getSelfServiceRegistrationFlow(String(flowId))
|
||||||
|
} else {
|
||||||
|
serviceFlow =
|
||||||
|
await ory.initializeSelfServiceRegistrationFlowForBrowsers(
|
||||||
|
returnTo ? String(returnTo) : undefined,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlow(serviceFlow.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func()
|
||||||
|
}, [flowId, router, router.isReady, returnTo, flow])
|
||||||
|
|
||||||
|
const submitData = async (data: SubmitSelfServiceRegistrationFlowBody) => {
|
||||||
|
await router.push(`/signup?flow=${flow?.id}`, undefined, {
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const resp = await ory.submitSelfServiceRegistrationFlow(
|
||||||
|
String(flow?.id),
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
// TODO: handle registration
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: handle errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { flow, submitData }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSignUpFlow
|
@ -38,7 +38,7 @@
|
|||||||
"cypress": "^9.6.0",
|
"cypress": "^9.6.0",
|
||||||
"eslint": "7.32.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-config-next": "12.0.1",
|
"eslint-config-next": "12.0.1",
|
||||||
"ory-prettier-styles": "^1.1.2",
|
"ory-prettier-styles": "^1.3.0",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.18",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
|
@ -1,55 +1,29 @@
|
|||||||
import { ArrowRightIcon, HeartIcon, UserIcon } from '@heroicons/react/24/outline';
|
import Button from "../../components/common/Button"
|
||||||
|
import ChatMessage from "../../components/message/ChatMessage"
|
||||||
import Button from '../../components/Button';
|
import Input from "../../components/common/Input"
|
||||||
import ChatMessage from '../../components/ChatMessage';
|
import streams from "../../placeholder/GetStreams"
|
||||||
import Input from '../../components/Input';
|
import { NextPage } from "next"
|
||||||
import { numFormatter } from '../../utils/format';
|
import BrowseLayout from "../../components/layout/BrowseLayout"
|
||||||
import streams from '../../placeholder/GetStreams';
|
|
||||||
import { NextPage } from 'next';
|
|
||||||
import BrowseLayout from '../../components/BrowseLayout';
|
|
||||||
|
|
||||||
const ChannelPage: NextPage = () => {
|
const ChannelPage: NextPage = () => {
|
||||||
const stream = streams.data[1];
|
const stream = streams.data[1]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowseLayout>
|
<BrowseLayout>
|
||||||
<div className="flex-1 flex flex-row">
|
<div className="flex-1 flex flex-row">
|
||||||
<div className="bg-neutral-900 flex-1">
|
<div className="bg-neutral-900 flex-1 flex flex-col">
|
||||||
<div className="w-full h-auto aspect-video bg-red-200 " />
|
<div className="w-full bg-red-200 flex-1" />
|
||||||
<div className="flex flex-row p-4 space-x-3">
|
<div className="flex flex-row p-2 items-center justify-between">
|
||||||
<div className="w-20 h-20 bg-yellow-300 rounded-full" />
|
<div className="flex flex-row items-center space-x-3">
|
||||||
<div className="flex-1">
|
<span className="w-8 h-8 bg-yellow-300 rounded-full" />
|
||||||
<div className="flex flex-row justify-between items-center">
|
<span className="font-bold">{stream.user_name}</span>
|
||||||
<div className="font-bold">{stream.user_name}</div>
|
|
||||||
<div>
|
|
||||||
<Button className="h-8 w-10">
|
|
||||||
<HeartIcon className="text-gray-100 h-5 w-5 mx-auto" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row justify-between items-center">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="font-bold">{stream.title}</div>
|
|
||||||
<div className="text-violet-400">{stream.game_name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row items-center text-sm space-x-3">
|
|
||||||
<span>
|
|
||||||
<UserIcon className="h-5 w-5 inline-block" />
|
|
||||||
<span>{numFormatter.format(stream.viewer_count)}</span>
|
|
||||||
</span>
|
|
||||||
<span>{stream.started_at}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>1:14:32</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-zinc-900 w-80 border-l border-l-zinc-700 flex flex-col">
|
<div className="bg-zinc-900 w-80 border-l border-l-zinc-700 flex flex-col">
|
||||||
<div className="flex flex-row justify-between items-center border-b border-b-zinc-700 p-2">
|
<div className="flex flex-row justify-center items-center border-b border-b-zinc-700 p-2 h-12">
|
||||||
<Button variant="subtle" className="p-2">
|
|
||||||
<ArrowRightIcon className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
<p className="uppercase font-semibold text-sm">Stream Chat</p>
|
<p className="uppercase font-semibold text-sm">Stream Chat</p>
|
||||||
<div className="w-5" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-scrollbar">
|
<div className="flex-1 overflow-scrollbar">
|
||||||
{new Array(60).fill(0).map((_, i) => (
|
{new Array(60).fill(0).map((_, i) => (
|
||||||
@ -62,7 +36,7 @@ const ChannelPage: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BrowseLayout>
|
</BrowseLayout>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChannelPage;
|
export default ChannelPage
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { categories } from '../../placeholder/SearchCategories';
|
import { categories } from "../../placeholder/SearchCategories"
|
||||||
|
|
||||||
function ChannelPage() {
|
function ChannelPage() {
|
||||||
const category = categories.data[0];
|
const category = categories.data[0]
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-row">
|
<div className="flex-1 flex flex-row">
|
||||||
<div className="bg-neutral-900 flex-1 text-gray-100">
|
<div className="bg-neutral-900 flex-1 text-gray-100">
|
||||||
@ -20,7 +20,7 @@ function ChannelPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChannelPage;
|
export default ChannelPage
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { NextPage } from 'next';
|
import { NextPage } from "next"
|
||||||
|
|
||||||
import LoginModal from '../components/LoginModal';
|
import LoginModal from "../components/login/LoginModal"
|
||||||
|
|
||||||
const LoginPage: NextPage = () => {
|
const LoginPage: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-neutral-900 w-screen h-screen">
|
<div className="bg-neutral-900 w-screen h-screen">
|
||||||
<LoginModal isOpen={true} defaultPage={0} onClose={() => {}} />
|
<LoginModal isOpen={true} defaultPage={0} />
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
|
import { NextPage } from "next"
|
||||||
import { NextPage } from 'next';
|
import LoginModal from "../components/login/LoginModal"
|
||||||
import LoginModal from '../components/LoginModal';
|
|
||||||
|
|
||||||
const SignupPage: NextPage = () => {
|
const SignupPage: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-neutral-900 w-screen h-screen">
|
<div className="bg-neutral-900 w-screen h-screen">
|
||||||
<LoginModal isOpen={true} defaultPage={1} onClose={() => {}} />
|
<LoginModal isOpen={true} defaultPage={1} />
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default SignupPage;
|
export default SignupPage
|
||||||
|
2
client/pnpm-lock.yaml
generated
2
client/pnpm-lock.yaml
generated
@ -16,7 +16,7 @@ specifiers:
|
|||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-config-next: 12.0.1
|
eslint-config-next: 12.0.1
|
||||||
next: 12.1.5
|
next: 12.1.5
|
||||||
ory-prettier-styles: ^1.1.2
|
ory-prettier-styles: ^1.3.0
|
||||||
postcss: ^8.4.18
|
postcss: ^8.4.18
|
||||||
prettier: ^2.3.2
|
prettier: ^2.3.2
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
|
Reference in New Issue
Block a user