Front-end code for thecasualfunk.com.
nvm use
pnpm
, yarn
, and a few other package managers to the path:corepack enable
This project uses pnpm as the package manager. Server uses yarn.
pnpm install
You will first need to start the backend API locally. See the readme there for more details.
Once that is running, you can start the front-end locally with:
pnpm dev --host
The --host
flag is optional, but recommended if you want to access the app from other devices on the same network, which is a common scenario for this app.
This app has a few basic pages:
src/routes/root.tsx
.src/routes/SetList.tsx
/gigs/:gigSlug
src/routes/admin/events.tsx
/admin/events
src/routes/stage/EventStageScreen.tsx
/stage/:eventSlug
src/routes/fan/EventFanScreen.tsx
/e/:eventSlug
The basic idea of this app is to have "Events" which contain multiple "Engagements". These engagements are what the audience will see and interact with. At any given time, there is one active engagement for an event. Which engagement is active is managed on the admin pages (e.g. /admin/events/funksgiving
).
There are different types of engagements, and more will be added over time. The type determines what the audience will see and how they can interact with it.
One engagement type is "Photo Carousel". This is a simple engagement where the audience submits photos, which are then displayed as polaroids falling onto the stage screen.
Another is "Vote For", where the admin pre-configures a list of submissions and the audience votes for their favorite. This could also be considered a "poll" engagement.
Future ideas of engagements include:
Submissions are a generic concept/model that can be used by an engagement type. For example, in the "Photo Carousel" engagement type, submissions represent the photos submitted by the audience. In the "Vote For" engagement type, submissions represent the voting options, and in this case, audience members don't create submissions; only the admin does beforehand.
Submissions have a "data" property which can contain arbitrary data. For example, in the "Photo Carousel" engagement type, the data is the photo URL and a caption. In the "Vote For" engagement type, the data is the vote option text and a photo URL.
Reactions are similar to submissions, but they are a simpler model. They are used by the "Vote For" engagement type to represent the votes themselves.
Follow the instructions in the server's readme to add your new engagement type.
Create a new folder under src/engagements/
, e.g. src/engagements/raffle
.
Inside this folder, create a file called schema.ts
. This is where you will define the 5 GraphQL fragments for this engagement type's config, data, submission, and admin config data field types:
import { gql } from "@apollo/client";
gql`
fragment RaffleAdminConfigFields on RaffleAdminConfig {
# ...
}
fragment RaffleViewConfigFields on RaffleViewConfig {
# ...
}
fragment RaffleAdminDataFields on RaffleAdminData {
# ...
}
fragment RaffleViewDataFields on RaffleViewData {
# ...
}
fragment RaffleSubmissionFields on RaffleSubmissionData {
# ...
}
`;
AdminEngagementFragment
type in src/gql/fragments/AdminEngagementFragment.ts
FanEngagementFragment
type in src/gql/fragments/FanEngagementFragment.ts
StageEngagementFragment
type in src/gql/fragments/StageEngagementFragment.ts
AdminSubmissionFragment
type in src/gql/fragments/AdminSubmissionFragment.ts
FanSubmissionFragment
type in src/gql/fragments/FanSubmissionFragment.ts
StageSubmissionFragment
type in src/gql/fragments/StageSubmissionFragment.ts
fan
inside your new engagement folder, and create a view file that will be used to render the fan-facing part of the engagement. It should accept a prop of engagement
which will be the fan-facing data for the engagement. For example, assuming the name of your engagement is RAFFLE
, it might be FanRaffleEngagement.tsx
:export function FanRaffleEngagement({
engagement,
}: {
engagement: FanEngagementFragment;
}) {
return <div>Raffle screen!</div>;
}
stage
inside your new engagement folder, and create a view file that will be used to render the stage-facing part of the engagement. It should accept a prop of engagement
which will be the stage-facing data for the engagement. For example, assuming the name of your engagement is RAFFLE
, it might be StageRaffleEngagement.tsx
:export function StageRaffleEngagement({
engagement,
}: {
engagement: StageEngagementFragment;
}) {
return <div>Raffle stage screen!</div>;
}
definition.tsx
inside your new engagement folder. This is where you will define the engagement's definition:import {
EngagementType,
RaffleAdminConfig,
RaffleAdminData,
} from "@/gql/graphql";
import { StageRaffleEngagement } from "./stage/StageRaffleEngagement";
import { FanRaffleEngagement } from "./fan/FanRaffleEngagement";
import { EngagementDefinition } from "../base/EngagementDefinition";
import { CameraIcon } from "@radix-ui/react-icons";
export const raffleEngagementDefinition: EngagementDefinition<
RaffleAdminConfig,
RaffleAdminData
> = {
title: "Raffle",
icon: <CameraIcon />,
type: EngagementType.Raffle,
stageComponent: StageRaffleEngagement,
fanComponent: FanRaffleEngagement,
submissionsName: "Photos",
getInitialData: () => ({
// initial default data
}),
getInitialConfig: () => ({
// initial default config
}),
};
engagementDefinitionsArray
array in src/engagements/index.ts
.