Skip to content

Commit

Permalink
Standardize Sanity template (#32)
Browse files Browse the repository at this point in the history
* Standardize template

* Update Readme

* Add Validate Workflow

* Add script to generate environment file
  • Loading branch information
kenjonespizza authored Dec 19, 2024
1 parent 95d7b7f commit a14b6aa
Show file tree
Hide file tree
Showing 60 changed files with 23,284 additions and 163 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
name: Prettier

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # for checkout

jobs:
run:
name: Can the code be prettier? 🤔
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
node-version: lts/*
- run: pnpm install --dev --ignore-scripts
- uses: actions/cache@v4
with:
path: node_modules/.cache/prettier/.prettier-cache
key: prettier-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('.prettierignore') }}-${{ hashFiles('package.json') }}
- run: pnpm format
- run: git restore .github/workflows pnpm-lock.yaml
- uses: actions/create-github-app-token@v1
id: generate-token
with:
app-id: ${{ secrets.ECOSPARK_APP_ID }}
private-key: ${{ secrets.ECOSPARK_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7
with:
body: I ran `pnpm format` 🧑‍💻
branch: actions/prettier
commit-message: "chore(prettier): 🤖 ✨"
labels: 🤖 bot
sign-commits: true
title: "chore(prettier): 🤖 ✨"
token: ${{ steps.generate-token.outputs.token }}
12 changes: 12 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Validate Template
on: push

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate Sanity Template
uses: sanity-io/template-validator@v1
with:
repository: ${{ github.repository }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ testem.log
Thumbs.db
.vercel
.angular

# env files
.env
.env.*
!.env.example
106 changes: 70 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,90 @@
# A minimal Angular SPA with Sanity Studio
# Clean Angular + Sanity app

This starter uses [Angular](https://angular.io/) for the front end and [Sanity](https://www.sanity.io/) to handle its content.
This template includes a [Angular](https://angular.dev/) app with a [Sanity Studio](https://www.sanity.io/) – an open-source React application that connects to your Sanity project’s hosted dataset. The Studio is configured locally and can then be deployed for content collaboration.

## Featuring
## Features

- How to fetch content as data from [the Sanity Content Lake](https://www.sanity.io/docs/datastore)
- How to render block content with [Portable Text](https://www.sanity.io/docs/presenting-block-text)
- A [Sanity Studio](https://www.sanity.io/docs/sanity-studio) to create and edit content
- How to crop and render images with [Sanity Image URLs](https://www.sanity.io/docs/image-url)
- Fetch content seamlessly with [Sanity Content Lake](https://www.sanity.io/docs/datastore).
- Render beautiful block content using [Portable Text](https://www.sanity.io/docs/presenting-block-text).
- Manage and create content with the intuitive [Sanity Studio](https://www.sanity.io/docs/sanity-studio).
- Advanced image cropping and rendering via [Sanity Image URLs](https://www.sanity.io/docs/image-url).

> **Note**
>
> This starter features an `/app` and a `/studio` folder. The `/app` folder contains the frontend code, and the `/studio` folder contains the Sanity Studio code.
>
> This is **not** a monorepo setup. We put them both in one repository for the sake of simplicity. You might want to have separate repositories for each of the folders, to make it easier to deploy the app and the studio separately.
>
> This example runs primarly as a Single Page Application and is not suitable for a performant production website. You can look into [server- and pre-rendering Angular Universal apps](https://angular.io/guide/universal) if you plan to make it so.
## Demo

## Prerequisities
https://sanity-template-angular-clean.sanity.build

- [Node.js](https://nodejs.org/en/) (v16.12 or later)
- [Sanity CLI](https://www.sanity.io/docs/getting-started-with-sanity-cli) (optional)
## Getting Started

## Getting started
### Installing the template

The following instructions will take you in **both** the `/app` and `/studio` folders.
#### 1. Initialize template with Sanity CLI

1. `npm install` to install dependencies in both folders
2. In the `/studio` folder, run `npm create sanity@latest init --env` (or use `sanity init --env` if you have the CLI installed). This will:
Run the command in your Terminal to initialize this template on your local computer.

- ask you to select or create a Sanity project and dataset
- output a `.env` file with appropriate variables
See the documentation if you are [having issues with the CLI](https://www.sanity.io/help/cli-errors).

3. Copy your project ID and dataset name to the variables into `/app/src/environments/environments.ts`
4. From the `/studio` folder, run `npx sanity cors add http://localhost:4200 --no-credentials`, to allow your app to request data from the browser (Go here to learn more about [CORS](https://www.sanity.io/docs/cors))
5. `npm run dev` to start the development servers in both folders
```shell
npm create sanity@latest -- --template sanity-io/sanity-template-angular-clean
```

Your Angular app should now be running on [http://localhost:4200/](http://localhost:4200/) and Studio on [http://localhost:3333/](http://localhost:3333/).
#### 2. Run the application and Sanity Studio

_Feel free to move each folder to its separate location and check them into version control._
Navigate back to the template's root directory and start the development servers by running the following command

### Add content
```shell
npm run dev
```

1. Visit the Studio and create and publish a new `Post` document
2. Visit the App and refresh the page to see your content rendered on the page
#### 3. Open the app and sign in to the Studio

The schema for the `Post` document is defined in the `/studio/schemas` folder. You can add more documents and schemas to the Studio to suit your needs.
Open the Angular app running locally in your browser on [http://localhost:4200](http://localhost:4200).

## Deployments
Open the Studio running locally in your browser on [http://localhost:3333](http://localhost:3333). You should now see a screen prompting you to log in to the Studio. Use the same service (Google, GitHub, or email) that you used when you logged in to the CLI.

The `/app` and `/studio` folders are meant to be deployed separately.
### Adding content with Sanity

Feel free to deploy the App to whichever hosting provider you prefer, like [Vercel](https://vercel.com/), [Netlify](https://netlify.com), or [Cloudflare Pages](https://pages.cloudflare.com).
#### 1. Publish your first document

You can deploy the Sanity Studio by running `sanity deploy` in the `/studio` repository, provided you have the `sanity` package installed globally (`npm install --global sanity`). You can also run it with `npx sanity deploy` if you don't wish to install the CLI.
The template comes pre-defined with a schema containing a `Post` document type.

From the Studio, click "+ Create" and select the `Post` document type. Go ahead and create and publish the document.

Your content should now appear in your Angular app ([http://localhost:4200](http://localhost:4200)) as well as in the Studio on the "Presentation" Tab

#### 2. Extending the Sanity schema

The schema for the `Post` document type is defined in the `studio/src/schemaTypes/post.ts` file. You can [add more document types](https://www.sanity.io/docs/schema-types) to the schema to suit your needs.

### Deploying your application and inviting editors

#### 1. Deploy Sanity Studio

Your Angular frontend (`/angular-app`) and Sanity Studio (`/studio`) are still only running on your local computer. It's time to deploy and get it into the hands of other content editors.

Back in your Studio directory (`/studio`), run the following command to deploy your Sanity Studio.

```shell
npx sanity deploy
```

#### 2. Deploy Angular app to Vercel

You have the freedom to deploy your Angular app to your hosting provider of choice. With Vercel and GitHub being a popular choice, we'll cover the basics of that approach.

1. Create a GitHub repository from this project. [Learn more](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github).
2. Create a new Vercel project and connect it to your Github repository.
3. Set the `Root Directory` to your Angular app.
4. Configure your Environment Variables.

#### 3. Invite a collaborator

Now that you’ve deployed your Angular application and Sanity Studio, you can optionally invite a collaborator to your Studio. Open up [Manage](https://www.sanity.io/manage), select your project and click "Invite project members"

They will be able to access the deployed Studio, where you can collaborate together on creating content.

## Resources

- [Sanity documentation](https://www.sanity.io/docs)
- [Angular documentation](https://angular.dev/overview)
- [Join the Sanity Community](https://slack.sanity.io)
- [Learn Sanity](https://www.sanity.io/learn)
2 changes: 2 additions & 0 deletions angular-app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SANITY_PROJECT_ID=""
SANITY_DATASET=""
3 changes: 3 additions & 0 deletions app/.gitignore → angular-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ testem.log
Thumbs.db
.vercel
.angular

environment.ts
.env
File renamed without changes.
File renamed without changes.
File renamed without changes.
9 changes: 6 additions & 3 deletions app/package.json → angular-app/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"name": "angular-app",
"private": true,
"scripts": {
"ng": "ng",
"start": "ng serve",
"dev": "npm start",
"build": "ng build",
"dev": "npm run setEnvVars && npm start",
"build": "npm run setEnvVars && ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
"test": "ng test",
"setEnvVars": "node src/environments/setEnvVars.ts"
},
"dependencies": {
"@angular/animations": "^16.0.1",
Expand All @@ -23,6 +25,7 @@
"@portabletext/types": "^2.0.2",
"@sanity/client": "^6.0.1",
"@sanity/image-url": "^1.0.2",
"dotenv": "^16.4.7",
"express": "^4.15.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
Expand Down
26 changes: 26 additions & 0 deletions angular-app/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
import { PostComponent } from "./post/post.component";

const routes: Routes = [
{ path: "", component: HomeComponent, pathMatch: "full" },
{
path: "post/:slug",
component: PostComponent,
},
{
path: "**",
redirectTo: "404",
},
];

@NgModule({
imports: [
RouterModule.forRoot(routes, {
initialNavigation: "enabledBlocking",
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions angular-app/src/app/card/card.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<ng-container *ngIf="post">
<div class="card">
<img
*ngIf="post.mainImage"
class="card__cover"
alt="Cover image"
[src]="post.mainImage | sanityImage: 520"
/>
<div *ngIf="!post.mainImage" class="card__cover--none"></div>

<div class="card__container">
<h3 class="card__title">
<a
[routerLink]="'/post/' + post.slug.current"
class="card__link"
[href]="'/post/' + post.slug.current"
>
{{ post.title }}
</a>
</h3>
<p class="card__excerpt">{{ post.excerpt }}</p>
<p class="card__date"></p>
</div>
</div>
</ng-container>
File renamed without changes.
28 changes: 28 additions & 0 deletions angular-app/src/app/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, Input } from "@angular/core";
import { Post } from "src/types";

@Component({
selector: "app-card",
templateUrl: "./card.component.html",
styleUrls: ["./card.component.css"],
})
export class CardComponent {
@Input() post: Post;

constructor() {
this.post = {
_id: "1",
title: "My first post",
excerpt: "This is my first post.",
slug: {
current: "my-first-post",
},
mainImage: {
asset: {
url: "https://via.placeholder.com/400",
},
alt: "Placeholder image",
},
};
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
23 changes: 23 additions & 0 deletions angular-app/src/app/post/post.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<ng-container *ngIf="post">
<article class="post">
<img
*ngIf="post.mainImage"
class="post__cover"
[src]="post.mainImage | sanityImage: 1920"
alt="Cover image"
width="100%"
/>
<div *ngIf="!post.mainImage" class="post__cover--none"></div>

<div class="post__container">
<h1 class="post__title">{{ post.title }}</h1>
<p class="post__excerpt">{{ post.excerpt }}</p>
<p class="post__date"></p>
<div
*ngIf="post.body"
class="post__content"
[innerHTML]="post.body | portableTextToHTML"
></div>
</div>
</article>
</ng-container>
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Component, OnInit } from '@angular/core';
import { SanityService } from '../sanity.service';
import { ActivatedRoute } from '@angular/router';
import { Post } from 'src/types';
import { Component, OnInit } from "@angular/core";
import { SanityService } from "../sanity.service";
import { ActivatedRoute } from "@angular/router";
import { Post } from "src/types";

@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.css'],
selector: "app-post",
templateUrl: "./post.component.html",
styleUrls: ["./post.component.css"],
})
export class PostComponent implements OnInit {
post: Post;
slug: string = '';
slug: string = "";

constructor(
private sanityService: SanityService,
private route: ActivatedRoute
) {
this.slug = this.route.snapshot.params['slug'];
this.slug = this.route.snapshot.params["slug"];
}

ngOnInit(): void {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
Loading

0 comments on commit a14b6aa

Please sign in to comment.