First public commit

This commit is contained in:
Luke Vella 2022-04-12 07:14:28 +01:00
commit e05cd62e53
228 changed files with 17717 additions and 0 deletions

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 0a10 10 0 110 20 10 10 0 010-20zm4.905 3.68A8 8 0 003.68 14.906l3.296-3.296-.007-.022H5.531l-.253.835H4L5.44 8.06h1.62l.86 2.606.64-.64V8.06h1.662c.1 0 .197.004.291.013l4.393-4.393zm-4.908 7.738l1.222-1.223v.046c0 .305-.04.542-.121.712a.703.703 0 01-.35.36 1.32 1.32 0 01-.551.105h-.2zM8.99 12.423h1.248c.443 0 .828-.086 1.153-.26.325-.174.577-.424.754-.75.178-.326.266-.717.266-1.171 0-.395-.067-.74-.202-1.038l4.11-4.11A8 8 0 015.095 16.32l3.896-3.897zm5.802-3.332a.46.46 0 01.16.332h1.134a1.34 1.34 0 00-.214-.748 1.367 1.367 0 00-.594-.498A2.177 2.177 0 0014.365 8c-.345 0-.651.058-.918.175-.266.116-.474.279-.625.488a1.2 1.2 0 00-.221.726c-.002.341.108.61.33.808.223.196.528.336.914.42l.435.093c.162.036.29.074.385.115.096.04.164.086.205.137a.27.27 0 01.066.174.307.307 0 01-.068.19.43.43 0 01-.194.13.931.931 0 01-.318.047.985.985 0 01-.39-.07.558.558 0 01-.251-.207.668.668 0 01-.1-.337H12.49c.001.365.08.665.234.9.156.232.375.405.656.517.283.112.614.168.993.168.37 0 .686-.051.949-.155.264-.104.467-.255.609-.454.142-.199.214-.442.215-.729a1.337 1.337 0 00-.08-.46 1.055 1.055 0 00-.242-.38 1.475 1.475 0 00-.421-.295 2.691 2.691 0 00-.62-.203l-.358-.076a1.988 1.988 0 01-.269-.073.836.836 0 01-.185-.09.345.345 0 01-.107-.112.278.278 0 01-.028-.143.289.289 0 01.058-.17.361.361 0 01.17-.118.854.854 0 01.3-.044c.193 0 .335.04.43.119zm-8.526.17l.435 1.44h-.904l.435-1.44h.034z" clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

59
components/home/bonus.tsx Normal file
View file

@ -0,0 +1,59 @@
import * as React from "react";
import Ban from "./ban-ads.svg";
import Code from "@/components/icons/code.svg";
import Server from "@/components/icons/server.svg";
import CursorClick from "@/components/icons/cursor-click.svg";
const Bonus: React.VoidFunctionComponent = () => {
return (
<div className="py-16 max-w-7xl mx-auto px-8">
<h2 className="heading">Principles</h2>
<p className="subheading">We&apos;re not like the others</p>
<div className="grid grid-cols-4 gap-16">
<div className="col-span-4 md:col-span-2 lg:col-span-1">
<div className="mb-4 text-slate-400">
<CursorClick className="w-16" />
</div>
<h3 className="heading-sm">No login required</h3>
<div className="text text-base leading-relaxed">
We keep things simple and don&apos;t ask for more than what we need.
</div>
</div>
<div className="col-span-4 md:col-span-2 lg:col-span-1">
<div className="mb-4 text-slate-400">
<Code className="w-16" />
</div>
<h3 className="heading-sm">Open-source</h3>
<div className="text text-base leading-relaxed">
The codebase is fully open-source and{" "}
<a href="https://github.com/lukevella/Rallly">
available on github
</a>
.
</div>
</div>
<div className="col-span-4 md:col-span-2 lg:col-span-1">
<div className="mb-4 text-slate-400">
<Server className="w-16" />
</div>
<h3 className="heading-sm">Self-hostable</h3>
<div className="text text-base leading-relaxed">
Run it on your own server to get full control of your data.
</div>
</div>
<div className="col-span-4 md:col-span-2 lg:col-span-1">
<div className="mb-4 text-slate-400">
<Ban className="w-16" />
</div>
<h3 className="heading-sm">Ad-free</h3>
<div className="text text-base leading-relaxed">
You can give your ad-blocker a rest &ndash; You won&apos;t need it
here.
</div>
</div>
</div>
</div>
);
};
export default Bonus;

View file

@ -0,0 +1,64 @@
import Bell from "@/components/icons/bell.svg";
import Chat from "@/components/icons/chat.svg";
import Clock from "@/components/icons/clock.svg";
import DeviceMobile from "@/components/icons/device-mobile.svg";
import * as React from "react";
const Features: React.VoidFunctionComponent = () => {
return (
<div className="py-16 px-8 max-w-7xl mx-auto">
<h2 className="heading">Features</h2>
<p className="subheading">Everything you need to get the job done</p>
<div className="grid grid-cols-2 gap-12">
<div className="col-span-2 md:col-span-1">
<div className="p-3 bg-green-100/50 text-green-400 inline-block rounded-2xl mb-4">
<Clock className="w-8 h-8" />
</div>
<h3 className="heading-sm flex items-center">
Time slots
<span className="ml-2 font-normal text-sm bg-green-500 text-white rounded-full px-2 py-1">
New
</span>
</h3>
<p className="text">
If you need more granular options, Rallly lets you choose time slots
as options. If your participants are international, they can see
times in on their own time zone.
</p>
</div>
<div className="col-span-2 md:col-span-1">
<div className="p-3 bg-cyan-100/50 text-cyan-400 inline-block rounded-2xl mb-4">
<DeviceMobile className="w-8 h-8" />
</div>
<h3 className="heading-sm">Mobile friendly design</h3>
<p className="text">
Rallly is optimized to look and work great on mobile devices so you
and your participants can use it on the go.
</p>
</div>
<div className="col-span-2 md:col-span-1">
<div className="p-3 bg-rose-100/50 text-rose-400 inline-block rounded-2xl mb-4">
<Bell className="w-8 h-8" />
</div>
<h3 className="heading-sm">Notifications</h3>
<p className="text">
Need help staying on top of things? Rallly can send you an email
whenever participants vote or comment on your poll.
</p>
</div>
<div className="col-span-2 md:col-span-1">
<div className="p-3 bg-yellow-100/50 text-yellow-400 inline-block rounded-2xl mb-4">
<Chat className="w-8 h-8" />
</div>
<h3 className="heading-sm">Comments</h3>
<p className="text">
Got a question or just have something to say? You and your
participants can comment on polls to start a discussion.
</p>
</div>
</div>
</div>
);
};
export default Features;

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" fill="currentColor" class="text-purple-600 mr-3 text-opacity-50 transform">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.606 9.606 0 0112 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48C19.137 20.107 22 16.373 22 11.969 22 6.463 17.522 2 12 2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 898 B

80
components/home/hero.tsx Normal file
View file

@ -0,0 +1,80 @@
import { motion } from "framer-motion";
import { useTranslation } from "next-i18next";
import Link from "next/link";
import * as React from "react";
import { UserAvatarProvider } from "../poll/user-avatar";
import PollDemo from "./poll-demo";
import ScribbleArrow from "./scribble-arrow.svg";
const Hero: React.VoidFunctionComponent = () => {
const { t } = useTranslation("homepage");
const names = ["Peter", "Christine", "Samantha", "Joseph"];
return (
<div className="lg:flex p-8 items-end max-w-7xl mx-auto">
<div className="my-8 text-center lg:text-left">
<h1 className="text-5xl font-bold">
Schedule
<br />
<span className="text-indigo-500">group&nbsp;meetings</span>
<br />
with ease
</h1>
<div className="text-xl text-gray-400 mb-12">
Find the right date without the back and&nbsp;forth.
</div>
<div className="space-x-3">
<Link href="/new">
<a className="focus:ring-2 focus:ring-indigo-200 transition-all text-white bg-indigo-500 hover:bg-indigo-500/90 active:bg-indigo-600/90 px-5 py-3 font-semibold hover:text-white hover:no-underline shadow-sm hover:shadow-md rounded-lg">
{t("getStarted")}
</a>
</Link>
<Link href="/demo">
<a
className="text-white focus:ring-2 focus:ring-indigo-200 transition-all bg-slate-500 hover:bg-slate-500/90 active:bg-slate-600/90 px-5 py-3 font-semibold hover:text-white hover:no-underline shadow-sm hover:shadow-md rounded-lg"
rel="nofollow"
>
{t("viewDemo")}
</a>
</Link>
</div>
</div>
<div className="hidden mt-16 lg:mt-0 lg:ml-24 pointer-events-none select-none h-[380px] md:flex items-end justify-center">
<UserAvatarProvider seed="mock" names={names}>
<div className="inline-block relative">
<motion.div
className="absolute z-20 border-4 shadow-md bg-indigo-200/10 rounded-2xl border-indigo-500 h-full"
initial={{ opacity: 0, width: 100, scale: 1.2, translateX: 384 }}
animate={{ opacity: 1, scale: 1.1 }}
transition={{ type: "spring", delay: 1 }}
/>
<motion.div
className="absolute bg-indigo-500 text-slate-100 py-1 px-3 rounded-full z-20 text-sm"
initial={{
opacity: 0,
right: 190,
top: -65,
translateY: 50,
}}
animate={{ opacity: 1, translateY: 0 }}
transition={{ type: "spring", delay: 2 }}
>
Perfect! 🤩
<ScribbleArrow className="absolute text-slate-400 -right-8 top-3" />
</motion.div>
<motion.div
className="shadow-lg rounded-lg"
transition={{ type: "spring", delay: 0.5 }}
initial={{ opacity: 0, translateY: -100 }}
animate={{ opacity: 1, translateY: 0 }}
>
<PollDemo />
</motion.div>
</div>
</UserAvatarProvider>
</div>
</div>
);
};
export default Hero;

25
components/home/home.tsx Normal file
View file

@ -0,0 +1,25 @@
import Head from "next/head";
import React from "react";
import PageLayout from "../page-layout";
import Bonus from "./bonus";
import Features from "./features";
import Hero from "./hero";
const Home: React.VoidFunctionComponent = () => {
return (
<PageLayout>
<Head>
<title>Rallly - Schedule group meetings</title>
</Head>
<Hero />
<div className="bg-gradient-to-b from-transparent via-white to-white">
<Features />
</div>
<div className="bg-gradient-to-b from-white via-white to-transparent pb-16">
<Bonus />
</div>
</PageLayout>
);
};
export default Home;

View file

@ -0,0 +1,36 @@
import * as React from "react";
const HowItWorks: React.VoidFunctionComponent = () => {
return (
<div className="bg-gradient-to-b from-transparent via-white to-white">
<div className="py-16 px-8 mx-auto max-w-7xl">
<h2 className="heading text-center">How it works</h2>
<p className="subheading text-center">It&#39;s simple!</p>
<div className="grid grid-cols-3 gap-16">
<div className="col-span-1">
<h3 className="text-xl">Create a poll</h3>
<p className="text">
Choose options you would like your participants to choose from.
</p>
</div>
<div className="col-span-1">
<h3 className="text-xl">Share your link</h3>
<p className="text">
Share your unique link with your participants to give them access
to the page.
</p>
</div>
<div className="col-span-1">
<h3 className="text-xl">Vote</h3>
<p className="text">
Participants vote for the dates they prefer. The option with the
most votes wins!
</p>
</div>
</div>
</div>
</div>
);
};
export default HowItWorks;

1
components/home/index.ts Normal file
View file

@ -0,0 +1 @@
export { default } from './home';

View file

@ -0,0 +1,121 @@
import { format } from "date-fns";
import { useTranslation } from "next-i18next";
import * as React from "react";
import { useTimeoutFn } from "react-use";
import DateCard from "../date-card";
import Score from "../poll/score";
import UserAvater from "../poll/user-avatar";
import VoteIcon from "../poll/vote-icon";
const sidebarWidth = 180;
const participants = [
{
name: "Reed",
color: "bg-sky-400",
votes: [0, 2],
},
{
name: "Susan",
color: "bg-blue-400",
votes: [0, 1, 2],
},
{
name: "Johnny",
color: "bg-indigo-400",
votes: [2, 3],
},
{
name: "Ben",
color: "bg-purple-400",
votes: [0, 1, 2, 3],
},
];
const options = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
const PollDemo: React.VoidFunctionComponent = () => {
const { t } = useTranslation("app");
const [bestOption, setBestOption] = React.useState<number>();
useTimeoutFn(() => {
setBestOption(2);
}, 1500);
return (
<div
className="rounded-lg bg-white border shadow-md"
style={{ width: 600 }}
>
<div className="flex border-b shadow-sm">
<div
className="flex items-center pl-4 pr-2 py-4 shrink-0 font-medium"
style={{ width: sidebarWidth }}
>
<div className="grow h-full flex items-end">
{t("participantCount", { count: participants.length })}
</div>
</div>
{options.map((option, i) => {
const d = new Date(option);
let score = 0;
participants.forEach((participant) => {
if (participant.votes.includes(i)) {
score++;
}
});
return (
<div
key={i}
className="py-4 text-center shrink-0 transition-colors"
style={{ width: 100 }}
>
<DateCard
day={format(d, "dd")}
dow={format(d, "E")}
month={format(d, "MMM")}
annotation={
<Score count={score} highlight={i === bestOption} />
}
/>
</div>
);
})}
</div>
{participants.map((participant, i) => (
<div className="flex h-14" key={i}>
<div
className="flex items-center px-4 shrink-0"
style={{ width: sidebarWidth }}
>
<UserAvater
className="mr-2"
color={participant.color}
name={participant.name}
/>
<span className="truncate" title={participant.name}>
{participant.name}
</span>
</div>
<div className="flex">
{options.map((_, i) => {
return (
<div
key={i}
className="justify-center items-center flex shrink-0"
style={{ width: 100 }}
>
{participant.votes.some((vote) => vote === i) ? (
<VoteIcon type="yes" />
) : (
<VoteIcon type="no" />
)}
</div>
);
})}
</div>
</div>
))}
</div>
);
};
export default PollDemo;

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 30 30">
<path fill-rule="evenodd" d="M1.11 2.994h.001l.014-.002a17.546 17.546 0 011.383-.055c.902 0 2.175.06 3.643.3 2.944.48 6.612 1.674 9.67 4.498 1.464 1.35 2.55 3.281 3.339 5.492.787 2.204 1.259 4.626 1.535 6.894.27 2.224.35 4.275.36 5.788l-4.245-4.83a1 1 0 10-1.502 1.32l5.94 6.761a1 1 0 001.412.091l6.76-5.94a1 1 0 10-1.32-1.503l-5.045 4.433v-.262a53.57 53.57 0 00-.375-6.1c-.287-2.357-.783-4.935-1.637-7.325-.85-2.383-2.078-4.64-3.865-6.289-3.44-3.176-7.523-4.483-10.704-5.002A24.842 24.842 0 002.508.938a19.54 19.54 0 00-1.492.056 7.207 7.207 0 00-.089.008l-.025.003H.89v.001L1 2l-.11-.994a1 1 0 00.22 1.988" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 744 B

30
components/home/stats.tsx Normal file
View file

@ -0,0 +1,30 @@
import Link from "next/link";
import * as React from "react";
import { useTranslation } from "next-i18next";
const Stats: React.VoidFunctionComponent = () => {
const { t } = useTranslation("homepage");
return (
<div className="py-16">
<h2 className="heading text-center">Stats</h2>
<p className="subheading text-center">100,000+ polls created</p>
<div className="flex space-x-3 justify-center">
<Link href="/new">
<a className="focus:ring-2 focus:ring-indigo-200 transition-all text-white bg-indigo-500 hover:bg-indigo-500/90 active:bg-indigo-600/90 px-5 py-3 font-semibold hover:text-white hover:no-underline shadow-sm hover:shadow-md rounded-lg">
{t("getStarted")}
</a>
</Link>
<Link href="/demo">
<a
className="text-white focus:ring-2 focus:ring-indigo-200 transition-all bg-slate-500 hover:bg-slate-500/90 active:bg-slate-600/90 px-5 py-3 font-semibold hover:text-white hover:no-underline shadow-sm hover:shadow-md rounded-lg"
rel="nofollow"
>
{t("viewDemo")}
</a>
</Link>
</div>
</div>
);
};
export default Stats;