23 | This link unlocks {section?.name ?? '...'} when visited.
24 | >
25 | }
26 | icon={faLink}
27 | text={`${deck.urlWithOrigin}/u/${section?.id ?? 'error'}`}
28 | isShowing={isShowing}
29 | setIsShowing={setIsShowing}
30 | />
31 | )
32 |
33 | export default ShareSectionModal
34 |
--------------------------------------------------------------------------------
/components/Modal/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/z-index';
2 |
3 | .root {
4 | display: flex;
5 | position: fixed;
6 | justify-content: center;
7 | align-items: center;
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | left: 0;
12 | pointer-events: none;
13 | z-index: z-index.$modal;
14 | will-change: background;
15 | transition: background 0.3s;
16 |
17 | &[aria-hidden='false'] {
18 | pointer-events: all;
19 | background: transparentize(black, 0.5);
20 |
21 | .content {
22 | opacity: 1;
23 | transform: none;
24 | }
25 | }
26 | }
27 |
28 | .content {
29 | opacity: 0;
30 | transform: translateY(-100px);
31 | will-change: opacity, transform;
32 | transition: opacity 0.3s, transform 0.3s;
33 | }
34 |
--------------------------------------------------------------------------------
/components/NotFound/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/colors';
2 |
3 | .root {
4 | min-height: 100vh;
5 | background: colors.$light-gray;
6 | }
7 |
8 | .title {
9 | margin: 100px 30px 0 30px;
10 | text-align: center;
11 | font-size: 26px;
12 | font-weight: 900;
13 | color: white;
14 |
15 | @media (min-width: 360px) {
16 | margin-top: 120px;
17 | font-size: 30px;
18 | }
19 |
20 | @media (min-width: 470px) {
21 | margin-top: 140px;
22 | font-size: 40px;
23 | }
24 |
25 | @media (min-width: 740px) {
26 | font-size: 60px;
27 | }
28 |
29 | @media (min-width: 1300px) {
30 | margin-top: 180px;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/components/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 |
3 | import Head, { APP_SCHEMA } from 'components/Head'
4 | import TopGradient from 'components/TopGradient'
5 | import Navbar from 'components/Navbar'
6 |
7 | import styles from './index.module.scss'
8 |
9 | const NotFound: NextPage = () => (
10 |
11 | [[{ name: '404', url }]]}
15 | schema={[APP_SCHEMA]}
16 | />
17 |
18 |
19 | Oh no! Are you lost?
20 |
21 |
22 | )
23 |
24 | export default NotFound
25 |
--------------------------------------------------------------------------------
/components/Notification/index.module.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | font-weight: 900;
3 | }
4 |
5 | .body {
6 | margin: 2px 0 4px;
7 | font-size: 14px;
8 | opacity: 0.7;
9 | }
10 |
--------------------------------------------------------------------------------
/components/Notification/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './index.module.scss'
2 |
3 | export interface NotificationProps {
4 | title: string
5 | body: string
6 | }
7 |
8 | const Notification = ({ title, body }: NotificationProps) => (
9 | <>
10 | {title}
11 | {body}
12 | >
13 | )
14 |
15 | export default Notification
16 |
--------------------------------------------------------------------------------
/components/Notifications/Option/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/colors';
2 |
3 | .root {
4 | display: grid;
5 | grid:
6 | 'input label' auto
7 | '_ data' auto /
8 | auto 1fr;
9 |
10 | & + & {
11 | margin-top: 12px;
12 | }
13 | }
14 |
15 | .input {
16 | grid-area: input;
17 | height: max-content;
18 | margin: 6px 8px 0 0;
19 | }
20 |
21 | .label {
22 | grid-area: label;
23 | color: colors.$dark-gray;
24 | }
25 |
26 | .name,
27 | .info {
28 | display: block;
29 | max-width: 480px;
30 | }
31 |
32 | .name {
33 | font-weight: 900;
34 | }
35 |
36 | .info {
37 | font-size: 14px;
38 | }
39 |
40 | .data {
41 | grid-area: data;
42 | margin-top: 8px;
43 | transition: opacity 0.3s;
44 |
45 | &[aria-disabled='true'] {
46 | pointer-events: none;
47 | opacity: 0.5;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/components/Policy/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/colors';
2 |
3 | .root {
4 | padding: 30px;
5 | color: colors.$dark-gray;
6 | }
7 |
8 | .title {
9 | text-align: center;
10 | }
11 |
12 | .divider {
13 | margin: 30px 0;
14 | background: transparentize(colors.$dark-gray, 0.8);
15 | }
16 |
--------------------------------------------------------------------------------
/components/Policy/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react'
2 |
3 | import Head from '../Head'
4 |
5 | import styles from './index.module.scss'
6 |
7 | export interface PolicyProps {
8 | url?: string
9 | description: string
10 | title: string
11 | children?: ReactNode
12 | }
13 |
14 | const Policy = ({ url, title, description, children }: PolicyProps) => (
15 |
16 | [[{ name: title, url }]]}
21 | schema={[
22 | {
23 | '@type': 'Article',
24 | headline: title,
25 | name: title,
26 | description
27 | }
28 | ]}
29 | />
30 |
{title}
31 |
32 | {children}
33 |
34 | )
35 |
36 | export default Policy
37 |
--------------------------------------------------------------------------------
/components/Progress/constants.ts:
--------------------------------------------------------------------------------
1 | export const START_POSITION = 0.3
2 | export const DELAY = 200
3 |
--------------------------------------------------------------------------------
/components/Progress/index.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/z-index';
2 |
3 | $height: 3px;
4 | $color: white;
5 |
6 | #nprogress {
7 | position: relative;
8 | pointer-events: none;
9 | z-index: z-index.$progress;
10 |
11 | .bar {
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | width: 100%;
16 | height: $height;
17 | background: white;
18 | }
19 |
20 | .peg {
21 | display: block;
22 | position: absolute;
23 | right: 0px;
24 | width: 100px;
25 | height: 100%;
26 | box-shadow: 0 0 10px $color, 0 0 5px $color;
27 | opacity: 1;
28 | transform: rotate(3deg) translate(0px, -4px);
29 | }
30 |
31 | .spinner {
32 | display: block;
33 | position: fixed;
34 | top: 15px;
35 | right: 15px;
36 | }
37 |
38 | .spinner-icon {
39 | width: 18px;
40 | height: 18px;
41 | border: solid 2px transparent;
42 | border-top-color: $color;
43 | border-left-color: $color;
44 | border-radius: 50%;
45 | animation: nprogress-spinner 400ms linear infinite;
46 |
47 | @keyframes nprogress-spinner {
48 | to {
49 | transform: rotate(1turn);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/components/ReportMessage/data.ts:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 |
3 | import { ReportMessageQuery, ReportMessageProps } from './models'
4 | import users from 'lib/cache/users'
5 | import messages from 'lib/cache/messages'
6 |
7 | export const getServerSideProps: GetServerSideProps<
8 | ReportMessageProps,
9 | ReportMessageQuery
10 | > = async ({ params }) => {
11 | if (!params) return { notFound: true }
12 | const { fromId, toId, messageId } = params
13 |
14 | if (fromId === toId)
15 | return {
16 | redirect: { permanent: true, destination: '/' }
17 | }
18 |
19 | const [from, to, message] = await Promise.all([
20 | users.get(fromId),
21 | users.get(toId),
22 | messages.get(messageId)
23 | ])
24 |
25 | if (!(from && to && message)) return { notFound: true }
26 |
27 | if (!(message.from === from.id && message.to === to.id))
28 | return {
29 | redirect: { permanent: true, destination: '/' }
30 | }
31 |
32 | return {
33 | props: { from }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/components/ReportMessage/index.module.scss:
--------------------------------------------------------------------------------
1 | .reason {
2 | margin-top: 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/components/ReportMessage/models.ts:
--------------------------------------------------------------------------------
1 | import { ParsedUrlQuery } from 'querystring'
2 |
3 | import UserData from 'models/User/Data'
4 |
5 | export interface ReportMessageQuery extends ParsedUrlQuery {
6 | fromId: string
7 | toId: string
8 | messageId: string
9 | }
10 |
11 | export interface ReportMessageProps {
12 | from: UserData
13 | }
14 |
--------------------------------------------------------------------------------
/components/RestrictContact/data.ts:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from 'next'
2 |
3 | import { RestrictContactQuery } from './models'
4 | import users from 'lib/cache/users'
5 |
6 | export const getServerSideProps: GetServerSideProps<
7 | Record,
8 | RestrictContactQuery
9 | > = async ({ params }) => {
10 | if (!params) return { notFound: true }
11 |
12 | const user = await users.get(params.id)
13 | if (!user) return { notFound: true }
14 |
15 | return { props: {} }
16 | }
17 |
--------------------------------------------------------------------------------
/components/RestrictContact/models.ts:
--------------------------------------------------------------------------------
1 | import { ParsedUrlQuery } from 'querystring'
2 |
3 | export interface RestrictContactQuery extends ParsedUrlQuery {
4 | id: string
5 | }
6 |
--------------------------------------------------------------------------------
/components/Root/index.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import dynamic from 'next/dynamic'
3 |
4 | import expectsSignIn from 'lib/expectsSignIn'
5 | import useLayoutAuthState from 'hooks/useLayoutAuthState'
6 |
7 | const Dashboard = dynamic(() => import('components/Dashboard/Home'))
8 | const Landing = dynamic(() => import('components/Home'))
9 |
10 | interface RootProps {
11 | auth: boolean
12 | }
13 |
14 | const Root: NextPage = ({ auth: initialAuthState }) =>
15 | useLayoutAuthState() ?? initialAuthState ? (
16 |
17 | ) : (
18 |
19 | )
20 |
21 | Root.getInitialProps = async context => ({
22 | auth: expectsSignIn(context) ?? false
23 | })
24 |
25 | export default Root
26 |
--------------------------------------------------------------------------------
/components/SectionHeader/ToggleExpandedButton/index.module.scss:
--------------------------------------------------------------------------------
1 | $dimension: 30px;
2 |
3 | .root {
4 | flex-shrink: 0;
5 | width: $dimension;
6 | height: $dimension;
7 | border: 1.5px solid #ddd;
8 | border-radius: 50%;
9 | transition: opacity 0.3s, transform 0.3s;
10 |
11 | &:hover {
12 | opacity: 0.5;
13 | }
14 | }
15 |
16 | .icon {
17 | color: #582efe;
18 | }
19 |
--------------------------------------------------------------------------------
/components/SectionHeader/ToggleExpandedButton/index.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
2 | import { faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'
3 |
4 | import styles from './index.module.scss'
5 |
6 | export interface SectionHeaderToggleExpandedButtonProps {
7 | degrees: number
8 | toggle?(): void
9 | children: boolean
10 | }
11 |
12 | const SectionHeaderToggleExpandedButton = ({
13 | degrees,
14 | toggle,
15 | children: isExpanded
16 | }: SectionHeaderToggleExpandedButtonProps) => (
17 |
22 |
26 |
27 | )
28 |
29 | export default SectionHeaderToggleExpandedButton
30 |
--------------------------------------------------------------------------------
/components/SectionHeader/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/colors';
2 |
3 | $margin: 12px;
4 |
5 | .root {
6 | display: flex;
7 | align-items: center;
8 | overflow-x: auto;
9 | cursor: pointer;
10 | font-size: 18px;
11 |
12 | @media (min-width: 800px) {
13 | overflow-x: unset;
14 | }
15 | }
16 |
17 | .name {
18 | font-weight: bold;
19 | color: colors.$dark-gray;
20 | }
21 |
22 | .divider,
23 | .cards,
24 | .share {
25 | flex-shrink: 0;
26 | }
27 |
28 | .divider {
29 | $height: 2px;
30 |
31 | flex-grow: 1;
32 | min-width: 30px;
33 | height: $height;
34 | margin: 0 12px;
35 | background: #ddd;
36 | border-radius: $height / 2;
37 | }
38 |
39 | .cards {
40 | display: none;
41 | margin-right: 12px;
42 | font-weight: bold;
43 | color: #582efe;
44 | transform: translateY(-2px);
45 |
46 | @media (min-width: 470px) {
47 | display: block;
48 | }
49 | }
50 |
51 | .share {
52 | margin: 0 4px 0 14px;
53 | transform: scale(1.3);
54 |
55 | &:hover .shareIcon {
56 | opacity: 0.5;
57 | }
58 | }
59 |
60 | .shareIcon {
61 | fill: #4355f9;
62 | transition: opacity 0.3s;
63 | }
64 |
--------------------------------------------------------------------------------
/components/Stars/Star/index.module.scss:
--------------------------------------------------------------------------------
1 | $aspect-ratio: 26.2px / 25px;
2 | $default-height: 25px;
3 |
4 | .root {
5 | flex-shrink: 0;
6 | display: grid;
7 | width: calc(#{$aspect-ratio} * var(--star-height, #{$default-height}));
8 | height: var(--star-height, $default-height);
9 |
10 | &:not(:last-child) {
11 | margin-right: 2px;
12 | }
13 | }
14 |
15 | .background,
16 | .root > picture {
17 | grid-row: 1;
18 | grid-column: 1;
19 | }
20 |
21 | .background {
22 | background: #00d388;
23 | }
24 |
--------------------------------------------------------------------------------
/components/Stars/Star/index.tsx:
--------------------------------------------------------------------------------
1 | import Img from 'react-optimized-image'
2 |
3 | import star from 'images/icons/star.jpg'
4 | import styles from './index.module.scss'
5 |
6 | export interface StarProps {
7 | fill: number
8 | }
9 |
10 | const Star = ({ fill }: StarProps) => (
11 |
12 |
13 |
14 |
15 | )
16 |
17 | export default Star
18 |
--------------------------------------------------------------------------------
/components/Stars/index.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | display: flex;
3 | align-items: center;
4 | }
5 |
--------------------------------------------------------------------------------
/components/Stars/index.tsx:
--------------------------------------------------------------------------------
1 | import cx from 'classnames'
2 |
3 | import Star from './Star'
4 |
5 | import styles from './index.module.scss'
6 |
7 | const STARS = [0, 1, 2, 3, 4] as const
8 |
9 | export interface StarsProps {
10 | className?: string
11 | children: number
12 | }
13 |
14 | const Stars = ({ className, children: rating }: StarsProps) => (
15 |
16 | {STARS.map(offset => (
17 |
21 | ))}
22 |
23 | )
24 |
25 | export default Stars
26 |
--------------------------------------------------------------------------------
/components/Support/index.tsx:
--------------------------------------------------------------------------------
1 | import Policy from 'components/Policy'
2 |
3 | const Support = () => (
4 |
8 | Email us at support@memorize.ai or by post to:
9 |
10 |
11 | memorize.ai
12 |
13 | 1717 Curtis Avenue
14 |
15 | Manhattan Beach, CA 90266
16 |
17 | United States
18 |
19 | )
20 |
21 | export default Support
22 |
--------------------------------------------------------------------------------
/components/TextArea/index.module.scss:
--------------------------------------------------------------------------------
1 | @use 'styles/colors';
2 |
3 | .root {
4 | width: 100%;
5 | padding: 0.25rem 0.5rem;
6 | color: colors.$dark-gray;
7 | border: 2px solid colors.$gray-300;
8 | border-radius: 0.25rem;
9 | outline: none;
10 |
11 | &:hover {
12 | border-color: colors.$gray-400;
13 | }
14 |
15 | &:focus {
16 | border-color: colors.$blue-400;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/components/TextArea/index.tsx:
--------------------------------------------------------------------------------
1 | import { TextareaHTMLAttributes } from 'react'
2 | import cx from 'classnames'
3 |
4 | import styles from './index.module.scss'
5 |
6 | export interface TextAreaProps
7 | extends TextareaHTMLAttributes {
8 | className?: string
9 | minHeight?: string | number
10 | placeholder?: string
11 | value: string
12 | setValue: (value: string) => void
13 | }
14 |
15 | const TextArea = ({
16 | className,
17 | minHeight,
18 | placeholder,
19 | value,
20 | setValue,
21 | ...props
22 | }: TextAreaProps) => (
23 |